diff --git a/CHANGELOG.md b/CHANGELOG.md index eb40efb8..d28b54c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Version History --------------- +### Open VKL 1.1.0 + +- vklExamples improvements: asynchronous rendering, multiple viewports, + docking, and more +- Fixed bug in `openvkl_utility_vdb` which could lead to crashes when creating + VDB volumes with temporally constant tiles +- Superbuild updates to latest versions of dependencies +- Minimum rkcommon version is now 1.8.0 + ### Open VKL 1.0.1 - Fixed issue in `structuredRegular` and `vdb` interval iterators that could diff --git a/CMakeLists.txt b/CMakeLists.txt index db3a89ed..2b7cc6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) ## Establish project ## -project(openvkl VERSION 1.0.1 LANGUAGES C CXX) +project(openvkl VERSION 1.1.0 LANGUAGES C CXX) ## Add openvkl specific macros ## @@ -47,7 +47,7 @@ openvkl_configure_build_type() openvkl_configure_global_build_flags() openvkl_configure_ispc_isa() -set(RKCOMMON_VERSION_REQUIRED 1.7.0) +set(RKCOMMON_VERSION_REQUIRED 1.8.0) find_package(rkcommon ${RKCOMMON_VERSION_REQUIRED} REQUIRED) get_target_property(RKCOMMON_INCLUDE_DIRS rkcommon::rkcommon INTERFACE_INCLUDE_DIRECTORIES) diff --git a/README.md b/README.md index add85015..85360efb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Intel® Open Volume Kernel Library -This is release v1.0.1 of Intel® Open VKL. For changes and new features +This is release v1.1.0 of Intel® Open VKL. For changes and new features see the [changelog](CHANGELOG.md). Visit http://www.openvkl.org for more information. @@ -33,6 +33,15 @@ example renderers to demonstrate how to best use the Open VKL API. ## Version History +### Open VKL 1.1.0 + + - vklExamples improvements: asynchronous rendering, multiple + viewports, docking, and more + - Fixed bug in `openvkl_utility_vdb` which could lead to crashes when + creating VDB volumes with temporally constant tiles + - Superbuild updates to latest versions of dependencies + - Minimum rkcommon version is now 1.8.0 + ### Open VKL 1.0.1 - Fixed issue in `structuredRegular` and `vdb` interval iterators that diff --git a/examples/interactive/BatchApplication.cpp b/examples/interactive/BatchApplication.cpp new file mode 100644 index 00000000..e9aac2b5 --- /dev/null +++ b/examples/interactive/BatchApplication.cpp @@ -0,0 +1,72 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "BatchApplication.h" +#include "renderer/Framebuffer.h" +#include "renderer/Renderer.h" +#include "renderer/Scene.h" +#include "renderer/Scheduler.h" + +#include "rkcommon/utility/SaveImage.h" + +namespace openvkl { + namespace examples { + + BatchApplication::BatchApplication() {} + + BatchApplication::~BatchApplication() {} + + void BatchApplication::run(Scene &scene) + { + if (scene.rendererTypes.empty()) { + scene.rendererTypes = {"density_path_tracer_ispc"}; + } + + auto &scheduler = scene.scheduler; + auto &volume = scene.volume; + + scene.rendererParams->fixedFramebufferSize = true; + const vec2i resolution = scene.rendererParams->framebufferSize; + + std::cout << "Creating VKL objects ..." << std::endl; + volume.updateVKLObjects(); + volume.printInfo(); + scene.camera->fitToScreen(volume.getBounds()); + scene.camera.incrementVersion(); + + for (const auto &type : scene.rendererTypes) { + auto rendererPtr = scene.createRenderer(type); + if (!rendererPtr) { + continue; + } + + Renderer &renderer = *(rendererPtr.get()); + // This call will resize the framebuffer to our desired output + // resolution. + renderer.getFramebuffer(resolution.x, resolution.y); + + const std::string filename = type + ".pfm"; + + std::cout << "Rendering with " << type << " ..." << std::endl; + + scheduler.start(renderer); + for (unsigned i = 0; i < scene.batchModeSpp; ++i) { + std::cout << "\r" << i << " / " << scene.batchModeSpp << " spp" + << std::flush; + scheduler.renderFrame(renderer); + } + scheduler.stop(renderer); + std::cout << std::endl; + + const auto &framebuffer = + renderer.getFramebuffer(resolution.x, resolution.y); + const auto &fb = framebuffer.getFrontBuffer(); + std::cout << "Writing " << filename << " ..." << std::endl; + rkcommon::utility::writePFM( + filename, fb.getWidth(), fb.getHeight(), fb.getRgba()); + } + } + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/BatchApplication.h b/examples/interactive/BatchApplication.h new file mode 100644 index 00000000..55d6aa6d --- /dev/null +++ b/examples/interactive/BatchApplication.h @@ -0,0 +1,27 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace openvkl { + namespace examples { + + struct Scene; + class Scheduler; + + class BatchApplication + { + public: + BatchApplication(); + ~BatchApplication(); + void run(Scene &scene); + }; + + } // namespace examples +} // namespace openvkl + + diff --git a/examples/interactive/CMakeLists.txt b/examples/interactive/CMakeLists.txt index dbec9dc5..1949e6d6 100644 --- a/examples/interactive/CMakeLists.txt +++ b/examples/interactive/CMakeLists.txt @@ -1,91 +1,69 @@ ## Copyright 2019-2021 Intel Corporation ## SPDX-License-Identifier: Apache-2.0 -## OpenGL dependency ## - -set(OpenGL_GL_PREFERENCE "LEGACY") -find_package(OpenGL 2 REQUIRED) - -## GLFW dependency ## - -find_package(glfw3 REQUIRED) - -## Example renderers ## - -include_directories_ispc( - ${CMAKE_SOURCE_DIR}/openvkl/include - ${CMAKE_SOURCE_DIR}/openvkl/devices/cpu/math - ${RKCOMMON_INCLUDE_DIRS} -) - -openvkl_add_library_ispc(vkl_example_renderers STATIC - renderers/Renderer.cpp - renderers/Renderer.ih - renderers/Renderer.ispc - - renderers/DensityPathTracer.cpp - renderers/DensityPathTracer.ispc - renderers/HitIterator.cpp - renderers/HitIterator.ispc - renderers/IntervalIteratorDebug.cpp - renderers/IntervalIteratorDebug.ispc - renderers/RayMarchIterator.cpp - renderers/RayMarchIterator.ispc -) - -target_include_directories(vkl_example_renderers PUBLIC ${ISPC_TARGET_DIR}) - -target_link_libraries(vkl_example_renderers PUBLIC openvkl openvkl_testing rkcommon::rkcommon) - - - -set(OPENVKL_IMGUI_ROOT "${CMAKE_CURRENT_LIST_DIR}/imgui-1.81" - CACHE PATH "Path to imgui.") - -add_library(imgui STATIC - ${OPENVKL_IMGUI_ROOT}/imgui.cpp - ${OPENVKL_IMGUI_ROOT}/imgui_draw.cpp - ${OPENVKL_IMGUI_ROOT}/imgui_tables.cpp - ${OPENVKL_IMGUI_ROOT}/imgui_widgets.cpp - ${OPENVKL_IMGUI_ROOT}/backends/imgui_impl_glfw.cpp - ${OPENVKL_IMGUI_ROOT}/backends/imgui_impl_opengl2.cpp) - -target_include_directories(imgui PUBLIC ${OPENVKL_IMGUI_ROOT}) -target_link_libraries(imgui PUBLIC glfw) - -## Interactive example app ## - -add_executable(vklExamples - window/ArcballCamera.cpp - window/VKLWindow.cpp - window/GLFWVKLWindow.cpp - vklExamples.cpp - window/TransferFunctionWidget.cpp - - ${VKL_RESOURCE} -) +add_subdirectory(renderer) + +### vklExamples app ## + +set(OpenGL_GL_PREFERENCE "GLVND") # Request new ABI, but this is only a hint. +find_package(OpenGL 2) + +if (OPENGL_FOUND) + add_library(vkl_opengl INTERFACE) + target_compile_definitions(vkl_opengl INTERFACE GL_SILENCE_DEPRECATION) + if (TARGET OpenGL::GL) + target_link_libraries(vkl_opengl INTERFACE OpenGL::GL) + else () + # Old versions of cmake do not create the GL targets. + target_link_libraries(vkl_opengl INTERFACE ${OPENGL_LIBRARIES}) + target_include_directories(vkl_opengl INTERFACE ${OPENGL_INCLUDE_DIR}) + endif () + + find_package(glfw3 REQUIRED) + + set(OPENVKL_IMGUI_ROOT "${CMAKE_CURRENT_LIST_DIR}/imgui-1.83" + CACHE PATH "Path to imgui.") + + add_library(imgui STATIC + ${OPENVKL_IMGUI_ROOT}/imgui.cpp + ${OPENVKL_IMGUI_ROOT}/imgui_draw.cpp + ${OPENVKL_IMGUI_ROOT}/imgui_tables.cpp + ${OPENVKL_IMGUI_ROOT}/imgui_widgets.cpp + ${OPENVKL_IMGUI_ROOT}/backends/imgui_impl_glfw.cpp + ${OPENVKL_IMGUI_ROOT}/backends/imgui_impl_opengl2.cpp) + + target_include_directories(imgui PUBLIC ${OPENVKL_IMGUI_ROOT}) + target_link_libraries(imgui PUBLIC glfw vkl_opengl) + target_compile_definitions(imgui PUBLIC VKL_HAVE_IMGUI) +endif() -target_link_libraries(vklExamples PRIVATE - openvkl_utility - openvkl_testing - imgui - vkl_example_renderers - ${OPENGL_LIBRARIES} -) +if (TARGET imgui) + add_executable(vklExamples + vklExamples.cpp + BatchApplication.cpp + InteractiveApplication.cpp + ParameterGui.cpp + RenderView.cpp + TransferFunctionWidget.cpp + ${VKL_RESOURCE} + ) -target_include_directories(vklExamples PRIVATE ${CMAKE_CURRENT_LIST_DIR}) -target_compile_definitions(vklExamples PRIVATE GL_SILENCE_DEPRECATION) + target_link_libraries(vklExamples + PRIVATE + imgui + vkl_example_renderers + ) -install(TARGETS vklExamples RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(TARGETS vklExamples + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif() -## Benchmark app ## +### vklBenchmark app ## if (BUILD_BENCHMARKS) add_executable(vklBenchmark - window/ArcballCamera.cpp - window/VKLWindow.cpp vklBenchmark.cpp - ${VKL_RESOURCE} ) @@ -96,7 +74,5 @@ if (BUILD_BENCHMARKS) vkl_example_renderers ) - target_include_directories(vklBenchmark PRIVATE ${CMAKE_CURRENT_LIST_DIR}) - install(TARGETS vklBenchmark RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/examples/interactive/InteractiveApplication.cpp b/examples/interactive/InteractiveApplication.cpp new file mode 100644 index 00000000..3b973d02 --- /dev/null +++ b/examples/interactive/InteractiveApplication.cpp @@ -0,0 +1,338 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "InteractiveApplication.h" +#include "RenderView.h" +#include "renderer/DensityPathTracer.h" +#include "renderer/Scene.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include // For dockspace API. + +#include + +namespace openvkl { + namespace examples { + + static void glfwError(int error, const char *description) + { + std::cerr << "[EE] " << error << " " << description << std::endl; + } + + InteractiveApplication::InteractiveApplication() + { + glfwSetErrorCallback(glfwError); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + } + + InteractiveApplication::~InteractiveApplication() + { + glfwTerminate(); + } + + void InteractiveApplication::initializeImgui() + { + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= + ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable + // Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= + ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform + // Windows + io.ConfigViewportsNoAutoMerge = true; + io.ConfigViewportsNoTaskBarIcon = true; + + // When dragging inside viewports, we do not want the window to move. + // Instead, we update the camera. + io.ConfigWindowsMoveFromTitleBarOnly = true; + + io.ConfigDragClickToInputText = true; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + // ImGui::StyleColorsClassic(); + + // When viewports are enabled we tweak WindowRounding/WindowBg so platform + // windows can look identical to regular ones. + ImGuiStyle &style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + + // Setup Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL2_Init(); + } + + void InteractiveApplication::finalizeImgui() + { + ImGui_ImplOpenGL2_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + } + + void InteractiveApplication::run(Scene &scene) + { + if (!createWindow(scene.disableVSync)) { + throw std::runtime_error("Failed to create main window."); + } + + initializeImgui(); + + views.clear(); + activeViews.clear(); + inactiveViews.clear(); + scheduler = &scene.scheduler; + + if (scene.rendererTypes.empty()) { + scene.rendererTypes = {"density_pathtracer_ispc"}; + } + + for (const auto &type : scene.supportedRendererTypes()) { + auto renderer = scene.createRenderer(type); + if (!renderer) { + continue; + } + + views.emplace_back( + rkcommon::make_unique(type, std::move(renderer))); + + const bool isVisible = (std::find(scene.rendererTypes.begin(), + scene.rendererTypes.end(), + type) != scene.rendererTypes.end()); + views.back()->getVisibleFlag() = isVisible; + + if (isVisible) { + activeViews.push_back(views.back().get()); + } else { + inactiveViews.push_back(views.back().get()); + } + } + + const ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Note: For initialization, we must build the volumes at least once. + // Also, the update code below will launch asynchronous render + // threads. + bool volumeNeedsUpdate = true; + + auto sceneParamsGui = ParameterGui::makeSceneParamsGui(&scene); + auto rendererParamsGui = ParameterGui::makeRendererParamsGui(&scene); + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + // When the volume or sampler needs to be updated, we stop all renderers + // first. This should happen infrequently. Note that it is much faster + // to update the sampler than to update the volume. + if (volumeNeedsUpdate) { + for (auto *v : activeViews) { + scheduler->stop(v->getRenderer()); + } + + auto &volume = scene.volume; + const bool volumeIsDirty = volume.volumeIsDirty(); + volume.updateVKLObjects(); + if (volumeIsDirty) { + volume.printInfo(); + } + scheduler->locked(scene.camera, [&]() { + scene.camera->fitToScreen(volume.getBounds()); + scene.camera.incrementVersion(); + }); + + for (auto *v : activeViews) { + scheduler->start(v->getRenderer()); + } + } + + // Start the Dear ImGui frame + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + ImGuiID leftNodeId = 0; + ImGuiID centerNodeId = 0; + ImGuiID rightNodeId = 0; + initDockspace(leftNodeId, centerNodeId, rightNodeId); + + ImGui::SetNextWindowDockID(leftNodeId, ImGuiCond_FirstUseEver); + ImGui::Begin("Settings"); + volumeNeedsUpdate = sceneParamsGui->draw(*scheduler); + ImGui::End(); + + ImGui::SetNextWindowDockID(rightNodeId, ImGuiCond_FirstUseEver); + ImGui::Begin("Renderer Controls"); + { + rendererParamsGui->draw(*scheduler); + + for (size_t i = 0; i < views.size(); ++i) { + auto &v = views[i]; + ImGui::PushID(i); + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::CollapsingHeader(v->getName().c_str(), 0)) { + ImGui::Checkbox("Enable", &v->getVisibleFlag()); + if (v->isVisible()) { + v->drawParameterGui(); + } + } + ImGui::PopID(); + } + enableVisibleRenderViews(); + } + ImGui::End(); + + for (auto *v : activeViews) { + ImGui::SetNextWindowDockID(centerNodeId, ImGuiCond_FirstUseEver); + v->draw(); + } + disableInvisibleRenderViews(); + + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(clear_color.x * clear_color.w, + clear_color.y * clear_color.w, + clear_color.z * clear_color.w, + clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + + ImGuiIO &io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + GLFWwindow *backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); + } + + glfwSwapBuffers(window); + } + + views.clear(); + activeViews.clear(); + inactiveViews.clear(); + scheduler = nullptr; + + finalizeImgui(); + + glfwDestroyWindow(window); + window = nullptr; + } + + bool InteractiveApplication::createWindow(bool disableVSync) + { + window = + glfwCreateWindow(1920, 1080, "Open VKL Examples", nullptr, nullptr); + if (!window) { + return false; + } + glfwMakeContextCurrent(window); + if (disableVSync) { + glfwSwapInterval(0); + } else { + glfwSwapInterval(1); + } + + return true; + } + + void InteractiveApplication::initDockspace(unsigned &leftNodeId, + unsigned ¢erNodeId, + unsigned &rightNodeId) + { + // The dock space is a window, but we do not want it to show any controls, + // or cover any other windows. + const ImGuiWindowFlags windowFlags = + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_MenuBar; + + ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + static const char *dockSpaceName = "VKLExamplesDocker"; + const ImGuiID dockSpaceId = ImGui::GetID(dockSpaceName); + + ImGui::Begin(dockSpaceName, nullptr, windowFlags); + + // Initially, we use only two nodes: left for scene controls, center + // for views. + if (ImGui::DockBuilderGetNode(dockSpaceId) == nullptr) { + ImGui::DockBuilderRemoveNode(dockSpaceId); + ImGui::DockBuilderAddNode(dockSpaceId, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockSpaceId, + ImGui::GetMainViewport()->Size); + + ImGuiID ctr = 0; + ImGui::DockBuilderSplitNode( + dockSpaceId, ImGuiDir_Left, 0.28f, &leftNodeId, &ctr); + + ImGui::DockBuilderSplitNode( + ctr, ImGuiDir_Right, 0.28f, &rightNodeId, ¢erNodeId); + + ImGui::DockBuilderFinish(dockSpaceId); + } + + // Create the actual dock space. + ImGui::DockSpace(dockSpaceId, ImVec2(0.f, 0.f), ImGuiDockNodeFlags_None); + + ImGui::End(); + ImGui::PopStyleVar(3); + } + + void InteractiveApplication::disableInvisibleRenderViews() + { + for (auto it = activeViews.begin(); it != activeViews.end();) { + auto *view = *it; + if (view->getVisibleFlag()) { + ++it; + } else { + scheduler->stop(view->getRenderer()); + inactiveViews.push_back(view); + it = activeViews.erase(it); + } + } + } + + void InteractiveApplication::enableVisibleRenderViews() + { + for (auto it = inactiveViews.begin(); it != inactiveViews.end();) { + auto *view = *it; + if (view->getVisibleFlag()) { + scheduler->start(view->getRenderer()); + activeViews.push_back(view); + it = inactiveViews.erase(it); + } else { + ++it; + } + } + } + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/InteractiveApplication.h b/examples/interactive/InteractiveApplication.h new file mode 100644 index 00000000..69fb70d9 --- /dev/null +++ b/examples/interactive/InteractiveApplication.h @@ -0,0 +1,47 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +struct GLFWwindow; + +namespace openvkl { + namespace examples { + + struct Scene; + class Scheduler; + class RenderView; + + class InteractiveApplication + { + public: + InteractiveApplication(); + ~InteractiveApplication(); + void run(Scene &scene); + + private: + void initializeImgui(); + void finalizeImgui(); + bool createWindow(bool disableVSync); + void initDockspace(unsigned &leftNodeId, + unsigned ¢erNodeId, + unsigned &rightNodeId); + void showContextMenu(); + void disableInvisibleRenderViews(); + void enableVisibleRenderViews(); + + private: + GLFWwindow *window{nullptr}; + Scheduler *scheduler{nullptr}; + std::list activeViews; + std::list inactiveViews; + std::vector> views; + }; + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/ParameterGui.cpp b/examples/interactive/ParameterGui.cpp new file mode 100644 index 00000000..0c17f5e3 --- /dev/null +++ b/examples/interactive/ParameterGui.cpp @@ -0,0 +1,757 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "ParameterGui.h" +#include "TransferFunctionWidget.h" + +#include "renderer/DensityPathTracer.h" +#include "renderer/HitIteratorRenderer.h" +#include "renderer/IntervalIteratorDebug.h" +#include "renderer/RayMarchIteratorRenderer.h" +#include "renderer/Renderer.h" +#include "renderer/Scene.h" +#include "renderer/Scheduler.h" + +#include +#include // memset +#include +#include + +namespace openvkl { + namespace examples { + + namespace imgui { + + inline bool inputText(const std::string &label, std::string &text) + { + constexpr size_t bufSize = 1024; + char buf[bufSize]; + std::memset(buf, 0, bufSize); + std::strncpy(buf, text.c_str(), bufSize - 1); + const bool changed = ImGui::InputText(label.c_str(), buf, bufSize); + if (changed) { + text = buf; + } + return changed; + } + + template + inline bool comboBox(const std::string &label, + T &output, + const std::vector &opts, + MapToLabel mapToString) + { + bool changed = false; + + if (ImGui::BeginCombo(label.c_str(), mapToString(output).c_str())) { + for (const T &opt : opts) { + bool isSelected = (output == opt); + if (ImGui::Selectable(mapToString(opt).c_str(), &isSelected)) { + changed = true; + output = opt; + } + // Scroll to item if selected! + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + return changed; + } + + inline bool comboBox(const std::string &label, + std::string &output, + const std::vector &opts) + { + return comboBox( + label, output, opts, [](const std::string &s) { return s; }); + } + + inline bool floatVector(const std::string &id, + std::vector &samples, + size_t minSize, + float minValue, + float maxValue) + { + bool changed = false; + ImGui::PushID(id.c_str()); + { + // First control: number of samples. + int numSamplesInput = static_cast(samples.size()); + if (ImGui::InputInt("##TS", &numSamplesInput)) { + numSamplesInput = std::max(0, numSamplesInput); + const size_t numSamples = + std::max(static_cast(numSamplesInput), minSize); + changed = (samples.size() != numSamples); + samples.resize(numSamples); + } + ImGui::PushID("Sliders"); + { + const ImGuiSliderFlags flags = ImGuiSliderFlags_AlwaysClamp; + std::vector mustDelete(samples.size(), false); + size_t numDelete = 0; + for (size_t i = 0; i < samples.size(); ++i) { + ImGui::PushID(i); + changed |= ImGui::SliderFloat("##timeSample", + &samples[i], + minValue, + maxValue, + "%.3f", + flags); + ImGui::PopID(); + } + std::vector newSamples; + newSamples.reserve(samples.size()); + for (size_t i = 0; i < samples.size(); ++i) { + if (!mustDelete[i]) { + newSamples.push_back(samples[i]); + } else { + changed = true; + } + } + using std::swap; + swap(samples, newSamples); + } + ImGui::PopID(); + } + ImGui::PopID(); + return changed; + } + + } // namespace imgui + + // ------------------------------------------------------------------------- + + class DensityPathTracerGui : public ParameterGui + { + public: + using P = DensityPathTracerParams; + + DensityPathTracerGui(const std::string &id, Versioned

*params) + : id{id}, params{params} + { + } + + bool drawImpl(P &p) + { + bool changed = false; + + ImGui::PushID(id.c_str()); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Motion")) { + changed |= ImGui::Checkbox("Motion Blur", &p.motionBlur); + ImGui::PushDisabled(!p.motionBlur); + changed |= ImGui::SliderFloat("Shutter", &p.shutter, 0.f, 1.f); + ImGui::PopDisabled(); + ImGui::TreePop(); + } + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Material")) { + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Coefficients")) { + changed |= ImGui::InputFloat("SigmaT Scale", &p.sigmaTScale); + changed |= ImGui::InputFloat("SigmaS Scale", &p.sigmaSScale); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Ambient Light")) { + changed |= ImGui::InputFloat("Intensity", &p.ambientLightIntensity); + ImGui::TreePop(); + } + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Light Transport")) { + changed |= + ImGui::SliderInt("Max Num Scatters", &p.maxNumScatters, 1, 128); + changed |= ImGui::Checkbox("Show Bounding Box", &p.showBbox); + ImGui::TreePop(); + } + + ImGui::PopID(); + + return changed; + } + + bool draw(const Scheduler &scheduler) override final + { + bool changed = false; + + scheduler.locked(*params, [&]() { + if (drawImpl(**params)) { + params->incrementVersion(); + changed = true; + } + }); + + return changed; + } + + private: + std::string id; + Versioned

*params{nullptr}; + }; + + // ------------------------------------------------------------------------- + + class HitIteratorRendererGui : public ParameterGui + { + public: + using P = HitIteratorRendererParams; + + HitIteratorRendererGui(const std::string &id, Versioned

*params) + : id{id}, params{params} + { + } + + bool drawImpl(P &p) + { + bool changed = false; + + ImGui::PushID(id.c_str()); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Isovalues")) { + constexpr size_t minSize = 1; + constexpr float minValue = -1.f; + constexpr float maxValue = 1.f; + changed |= imgui::floatVector( + "isovalues", p.isoValues, minSize, minValue, maxValue); + ImGui::TreePop(); + } + + ImGui::PopID(); + + return changed; + } + + bool draw(const Scheduler &scheduler) override final + { + bool changed = false; + + scheduler.locked(*params, [&]() { + if (drawImpl(**params)) { + params->incrementVersion(); + changed = true; + } + }); + + return changed; + } + + private: + std::string id; + Versioned

*params{nullptr}; + }; + + // ------------------------------------------------------------------------- + + class RayMarchIteratorRendererGui : public ParameterGui + { + public: + using P = RayMarchIteratorRendererParams; + + RayMarchIteratorRendererGui(const std::string &id, Versioned

*params) + : id{id}, params{params} + { + } + + bool drawImpl(P &p) + { + bool changed = false; + + ImGui::PushID(id.c_str()); + + changed |= + ImGui::SliderFloat("Sampling rate", &p.samplingRate, 0.01f, 4.f); + + ImGui::PopID(); + + return changed; + } + + bool draw(const Scheduler &scheduler) override final + { + bool changed = false; + + scheduler.locked(*params, [&]() { + if (drawImpl(**params)) { + params->incrementVersion(); + changed = true; + } + }); + + return changed; + } + + private: + std::string id; + Versioned

*params{nullptr}; + }; + + // ------------------------------------------------------------------------- + + class IntervalIteratorDebugGui : public ParameterGui + { + public: + using P = IntervalIteratorDebugParams; + + IntervalIteratorDebugGui(const std::string &id, Versioned

*params) + : id{id}, params{params} + { + } + + bool drawImpl(P &p) + { + bool changed = false; + + ImGui::PushID(id.c_str()); + + changed |= + ImGui::SliderFloat("Color scale", &p.intervalColorScale, 1.f, 32.f); + changed |= + ImGui::SliderFloat("Opacity", &p.intervalOpacity, 0.01f, 1.f); + changed |= ImGui::Checkbox("First interval only", &p.firstIntervalOnly); + changed |= + ImGui::Checkbox("Show interval borders", &p.showIntervalBorders); + + ImGui::PopID(); + + return changed; + } + + bool draw(const Scheduler &scheduler) override final + { + bool changed = false; + + scheduler.locked(*params, [&]() { + if (drawImpl(**params)) { + params->incrementVersion(); + changed = true; + } + }); + + return changed; + } + + private: + std::string id; + Versioned

*params{nullptr}; + }; + + // ------------------------------------------------------------------------- + + ParameterGui::~ParameterGui() {} + + template + inline void mrg(const std::string &id, + Renderer *renderer, + std::unique_ptr &gui) + { + if (!gui) { + R *r = dynamic_cast(renderer); + if (r) { + gui = rkcommon::make_unique(id, &r->getGuiParams()); + } + } + } + + std::unique_ptr ParameterGui::makeRendererGui( + Renderer *renderer) + { + std::unique_ptr gui; + + mrg( + "density_pathtracer", renderer, gui); + mrg( + "density_pathtracer_ispc", renderer, gui); + + mrg( + "hit_iterator", renderer, gui); + mrg( + "hit_iterator_ispc", renderer, gui); + + mrg( + "ray_march_iterator", renderer, gui); + mrg( + "ray_march_iterator_ispc", renderer, gui); + + mrg( + "interval_iterator_debug", renderer, gui); + mrg( + "interval_iterator_debug_ispc", renderer, gui); + + return gui; + } + + // ------------------------------------------------------------------------- + + class RendererParamsGui : public ParameterGui + { + public: + using P = RendererParams; + + RendererParamsGui(Scene *scene) + : scene{scene}, + tfWidget{scene->rendererParams->transferFunction.valueRange} + { + } + + bool drawImpl(P &p) + { + bool changed = false; + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::CollapsingHeader("Volume", 0)) { + changed |= ImGui::SliderFloat("Time", &p.time, 0.f, 1.f); + const unsigned numAttributes = scene->volume.getNumAttributes(); + const unsigned maxAttribute = + std::max(numAttributes, 1) - 1; + p.attributeIndex = + std::min(p.attributeIndex, static_cast(maxAttribute)); + ImGui::PushDisabled(numAttributes <= 1); + changed |= ImGui::SliderInt( + "Attribute Index", &p.attributeIndex, 0, maxAttribute); + ImGui::PopDisabled(); + + if (tfWidget.updateUI()) { + p.transferFunction = + TransferFunction(tfWidget.getValueRange(), + tfWidget.getSampledColorsAndOpacities()); + changed = true; + } + } + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::CollapsingHeader("Framebuffer", 0)) { + changed |= ImGui::Checkbox("Fixed Size", &p.fixedFramebufferSize); + ImGui::PushDisabled(!p.fixedFramebufferSize); + changed |= ImGui::InputInt2("##Size", &p.framebufferSize.x); + + changed |= + ImGui::Checkbox("Restrict Pixel Range", &p.restrictPixelRange); + ImGui::PushDisabled(!p.restrictPixelRange); + changed |= ImGui::DragIntRange2("X", + &p.pixelRange.lower.x, + &p.pixelRange.upper.x, + 1.f, + 0, + INT_MAX); + changed |= ImGui::DragIntRange2("Y", + &p.pixelRange.lower.y, + &p.pixelRange.upper.y, + 1.f, + 0, + INT_MAX); + ImGui::PopDisabled(); + ImGui::PopDisabled(); + } + return changed; + } + + bool draw(const Scheduler &scheduler) override final + { + bool changed = false; + + Versioned

¶ms = scene->rendererParams; + + scheduler.locked(params, [&]() { + if (drawImpl(*params)) { + params.incrementVersion(); + changed = true; + } + }); + + return changed; + } + + private: + Scene *scene{nullptr}; + TransferFunctionWidget tfWidget; + }; + + std::unique_ptr ParameterGui::makeRendererParamsGui( + Scene *scene) + { + return rkcommon::make_unique(scene); + } + + // ------------------------------------------------------------------------- + + class SceneParamsGui : public ParameterGui + { + public: + SceneParamsGui(Scene *scene) : scene{scene} {} + + bool drawImpl(VolumeParams &p) + { + bool needsUpdate = + false; // indicates that the user clicked the button. + bool &changed = volumeChanged; + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::CollapsingHeader("Volume", 0)) { + ImGui::PushDisabled(!changed); + needsUpdate = ImGui::Button( + "Rebuild Volume", ImVec2(ImGui::GetContentRegionAvail().x, 0)); + ImGui::PopDisabled(); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Type")) { + const bool wasSpherical = (p.volumeType == "structuredSpherical"); + changed |= + imgui::comboBox("##T", p.volumeType, p.supportedVolumeTypes()); + const bool isSpherical = (p.volumeType == "structuredSpherical"); + ImGui::TreePop(); + + // Structured spherical volumes have their own way of parametrizing + // the volume! Make sure changing this in the GUI works. + if (wasSpherical != isSpherical) { + p.gridOrigin.x = rkcommon::math::nan; + p.generateGridTransform(); + } + } + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Source")) { + const std::vector &supSources = + p.supportedSources(p.volumeType); + // Not all volume types support the same sources, so reset if an + // invalid source is currently selected. + if (std::find(supSources.begin(), supSources.end(), p.source) == + supSources.end()) { + p.source = supSources[0]; + } + changed |= imgui::comboBox( + "Field Source##FieldSource", p.source, supSources); + + const bool isFile = (p.source == "file"); + ImGui::PushDisabled(!isFile); + changed |= imgui::inputText("Filename", p.filename); + ImGui::PopDisabled(); + + if (isFile) { + ImGui::PushDisabled(p.volumeType != "vdb"); + changed |= imgui::inputText("Field##FieldInFile", p.fieldInFile); + ImGui::PopDisabled(); + } else { + const std::vector &supFields = + p.supportedFields(p.volumeType); + // Not all volume types support the same sources, so reset if an + // invalid source is currently selected. + if (std::find(supFields.begin(), supFields.end(), p.field) == + supFields.end()) { + p.field = supFields[0]; + } + changed |= + imgui::comboBox("Field##ProcField", p.field, supFields); + } + + const std::vector &voxelTypes = + p.supportedVoxelTypes(p.volumeType); + ImGui::PushDisabled(voxelTypes.empty() || p.source == "file"); + { + if (!voxelTypes.empty()) { + // Fix voxel type if needed. + if (std::find(voxelTypes.begin(), + voxelTypes.end(), + p.voxelType) == voxelTypes.end()) { + p.voxelType = voxelTypes[0]; + } + } + + changed |= imgui::comboBox( + "Voxel Type", p.voxelType, voxelTypes, voxelTypeToString); + } + ImGui::PopDisabled(); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Transform")) { + ImGui::PushDisabled(p.volumeType == "particle" || + p.source == "file"); + { + bool transformChanged = false; + transformChanged |= + ImGui::InputInt3("Grid Dimensions", &(p.dimensions.x)); + transformChanged |= + ImGui::InputFloat3("Grid Origin", &(p.gridOrigin.x)); + transformChanged |= + ImGui::InputFloat3("Grid Spacing", &(p.gridSpacing.x)); + if (transformChanged) { + p.generateGridTransform(); + } + changed |= transformChanged; + } + ImGui::PopDisabled(); + ImGui::TreePop(); + } + + ImGui::PushDisabled(p.volumeType != "particle"); + { + changed |= ImGui::InputInt("Particles", &p.numParticles); + } + ImGui::PopDisabled(); + + ImGui::PushDisabled(!(p.volumeType == "structuredRegular" || + p.volumeType == "vdb")); + { + changed |= + ImGui::Checkbox("Multiple Attributes", &p.multiAttribute); + } + ImGui::PopDisabled(); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Motion")) { + ImGui::PushDisabled(!(p.volumeType == "structuredRegular" || + p.volumeType == "vdb")); + { + if (ImGui::RadioButton("Temporally Constant", + !(p.motionBlurStructured || + p.motionBlurUnstructured))) { + changed = true; + p.motionBlurStructured = false; + p.motionBlurUnstructured = false; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Structured", p.motionBlurStructured)) { + changed = true; + p.motionBlurStructured = true; + p.motionBlurUnstructured = false; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Unstructured", + p.motionBlurUnstructured)) { + changed = true; + p.motionBlurStructured = false; + p.motionBlurUnstructured = true; + } + + ImGui::SetNextItemOpen(p.motionBlurStructured, + ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Structured Timesteps")) { + ImGui::PushDisabled(!p.motionBlurStructured); + { + int timesteps = p.motionBlurStructuredNumTimesteps; + if (ImGui::InputInt("##TS", ×teps)) { + p.motionBlurStructuredNumTimesteps = static_cast( + std::min(std::max(1, timesteps), 0xFF)); + changed = true; + } + } + ImGui::PopDisabled(); + ImGui::TreePop(); + } + + ImGui::SetNextItemOpen(p.motionBlurUnstructured, + ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Unstructured Timesteps")) { + ImGui::PushDisabled(!p.motionBlurUnstructured); + constexpr size_t minSize = 2; + constexpr float minValue = 0; + constexpr float maxValue = 1.f; + imgui::floatVector("unstructuredTimeSteps", + p.motionBlurUnstructuredTimeSamples, + minSize, + minValue, + maxValue); + ImGui::PopDisabled(); + ImGui::TreePop(); + } + } + ImGui::PopDisabled(); + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::TreeNode("Background")) { + // Make sure we use the correct NaN. + bool bgIsUndefined = std::isnan(p.background); + if (ImGui::Checkbox("Background Undefined", &bgIsUndefined)) { + changed = true; + if (bgIsUndefined) { + p.background = VKL_BACKGROUND_UNDEFINED; + } else { + p.background = 0.f; + } + } + ImGui::PushDisabled(bgIsUndefined); + { + changed |= ImGui::InputFloat("Background Value", &p.background); + } + ImGui::PopDisabled(); + ImGui::TreePop(); + } + } + + return changed && needsUpdate; + } + + bool drawImpl(const VolumeParams &volumeParams, SamplerParams &p) + { + bool changed = false; + + const std::vector &supFilters = + p.supportedFilters(volumeParams.volumeType); + + ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); + if (ImGui::CollapsingHeader("Sampler", 0)) { + ImGui::PushDisabled(volumeParams.volumeType != "vdb"); + changed |= ImGui::SliderInt("Maximum Sampling Depth##maxSamplingDepth", + &p.maxSamplingDepth, + 0, + VKL_VDB_NUM_LEVELS-1); + ImGui::PopDisabled(); + ImGui::PushDisabled(supFilters.empty()); + changed |= imgui::comboBox("Sampling Filter##samplingFilter", + p.filter, + supFilters, + filterToString); + changed |= imgui::comboBox("Gradient Filter##samplingFilter", + p.gradientFilter, + supFilters, + filterToString); + ImGui::PopDisabled(); + } + + return changed; + } + + bool draw(const Scheduler & /*scheduler*/) override final + { + bool changed = false; + + Volume &volume = scene->volume; + VolumeParams &volumeParams = volume.getVolumeParams(); + + if (drawImpl(volumeParams)) { + volume.setVolumeDirty(); + changed = true; + } + + SamplerParams &samplerParams = volume.getSamplerParams(); + if (drawImpl(volumeParams, samplerParams)) { + volume.setSamplerDirty(); + changed = true; + } + + return changed; + } + + private: + Scene *scene{nullptr}; + bool volumeChanged{false}; + }; + + std::unique_ptr ParameterGui::makeSceneParamsGui(Scene *scene) + { + return rkcommon::make_unique(scene); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/ParameterGui.h b/examples/interactive/ParameterGui.h new file mode 100644 index 00000000..d0806f31 --- /dev/null +++ b/examples/interactive/ParameterGui.h @@ -0,0 +1,33 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include // Push/PopDisabled + +#include + +namespace openvkl { + namespace examples { + + class Renderer; + class RendererParams; + class Scheduler; + class Scene; + + class ParameterGui + { + public: + virtual ~ParameterGui(); + virtual bool draw(const Scheduler &scheduler) = 0; + + // Factory methods. + static std::unique_ptr makeRendererGui(Renderer *renderer); + static std::unique_ptr makeRendererParamsGui(Scene *scene); + static std::unique_ptr makeSceneParamsGui(Scene *scene); + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/RenderView.cpp b/examples/interactive/RenderView.cpp new file mode 100644 index 00000000..25c0de5c --- /dev/null +++ b/examples/interactive/RenderView.cpp @@ -0,0 +1,259 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "RenderView.h" +#include "renderer/Renderer.h" + +namespace openvkl { + namespace examples { + + RenderView::RenderView(const std::string &name, + std::unique_ptr &&_renderer) + : name{name}, + toolboxName{name + "_TOOLBOX"}, + renderer{std::forward>(_renderer)} + { + parameterGui = ParameterGui::makeRendererGui(renderer.get()); + + glGenTextures(1, &fbTexId); + glBindTexture(GL_TEXTURE_2D, fbTexId); + // NEAREST is important, we prefer seeing what is actually in the + // framebuffer. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + RenderView::~RenderView() + { + glDeleteTextures(1, &fbTexId); + } + + bool getDragLine( + ImGuiIO &io, const vec2i &origin, int button, vec2f &from, vec2f &to) + { + const ImVec2 delta = ImGui::GetMouseDragDelta(button); + bool wasDragged = (delta.x != 0 && delta.y != 0); + if (wasDragged) { + const ImVec2 start = io.MouseClickedPos[button]; + + from = vec2f(start.x - origin.x, start.y - origin.y); + to = vec2f(start.x - origin.x + delta.x, start.y - origin.y + delta.y); + } + return wasDragged; + } + + bool RenderView::draw() + { + if (visible) { + ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + // I am unclear why this is required, but drawing vertically centered + // framebuffers that are too wide for the canvas somehow adds empty + // space at the bottom of the window. + const ImGuiWindowFlags flags = + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + auto &scene = getRenderer().getScene(); + auto &scheduler = scene.scheduler; + + scheduler.renderFrame(getRenderer()); + + if (ImGui::Begin(name.c_str(), &visible, flags)) { + auto &camera = scene.camera; + + const ImVec2 origin = ImGui::GetCursorScreenPos(); + const ImVec2 size = ImGui::GetContentRegionAvail(); + + // This button is here to catch mouse events, instead of just checking + // IsWindowFocused (which would rotate the camera when moving a + // floating window). + ImGui::InvisibleButton("canvas", + size, + ImGuiButtonFlags_MouseButtonLeft | + ImGuiButtonFlags_MouseButtonMiddle | + ImGuiButtonFlags_MouseButtonRight); + + const bool isHovered = ImGui::IsItemHovered(); + const bool isHeld = ImGui::IsItemActive(); + const vec2i vorg(origin.x, origin.y); + const vec2i vsize(size.x, size.y); + + ImGuiIO &io = ImGui::GetIO(); + vec2f from, to; + if (isHeld && + getDragLine(io, vorg, ImGuiMouseButton_Left, from, to)) { + scheduler.locked(camera, [&]() { + camera->rotate(from, to, vsize); + camera.incrementVersion(); + }); + ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left); + } + if (isHeld && + getDragLine(io, vorg, ImGuiMouseButton_Middle, from, to)) { + scheduler.locked(camera, [&]() { + camera->pan(from, to, vsize); + camera.incrementVersion(); + }); + ImGui::ResetMouseDragDelta(ImGuiMouseButton_Middle); + } + if (isHeld && + getDragLine(io, vorg, ImGuiMouseButton_Right, from, to)) { + scheduler.locked(camera, [&]() { + camera->dolly(from, to, vsize); + camera.incrementVersion(); + }); + ImGui::ResetMouseDragDelta(ImGuiMouseButton_Right); + } + + drawBackground(origin, size); + drawFramebuffer(scheduler, origin, size); + } + ImGui::End(); + ImGui::PopStyleVar(1); + } + return visible; + } + + void RenderView::drawParameterGui() + { + if (renderer && parameterGui) { + auto &scene = renderer->getScene(); + auto &scheduler = scene.scheduler; + parameterGui->draw(scheduler); + } + } + + void RenderView::drawBackground(const ImVec2 &canvasOrigin, + const ImVec2 &canvasSize) const + { + const ImVec2 p0 = canvasOrigin; + const ImVec2 p1(canvasOrigin.x + canvasSize.x, + canvasOrigin.y + canvasSize.y); + + ImGuiIO &io = ImGui::GetIO(); + ImDrawList *drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(p0, p1, IM_COL32(30, 30, 30, 255)); + } + + void RenderView::drawFramebuffer(const Scheduler &scheduler, + const ImVec2 &canvasOrigin, + const ImVec2 &canvasSize) const + { + if (canvasSize.x < 1 || canvasSize.y < 1) { + return; + } + + if (renderer) { + // Request a framebuffer that matches available space! + const auto &framebuffer = + renderer->getFramebuffer(canvasSize.x, canvasSize.y); + const auto &frontBuffer = framebuffer.getFrontBuffer(); + + size_t fbw = 0; + size_t fbh = 0; + Framebuffer::Stats stats; + + // Upload to GPU. + glBindTexture(GL_TEXTURE_2D, fbTexId); + scheduler.locked(frontBuffer, [&]() { + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + frontBuffer.getWidth(), + frontBuffer.getHeight(), + 0, + GL_RGBA, + GL_FLOAT, + frontBuffer.getRgba()); + fbw = frontBuffer.getWidth(); + fbh = frontBuffer.getHeight(); + stats = frontBuffer.getStats(); + }); + glBindTexture(GL_TEXTURE_2D, 0); + + // We have no guarantee that the renderer actually respected our + // width / height, so fit the resulting texture into the available + // space. + const float fbAspectRatio = fbw / static_cast(fbh); + const float caAspectRatio = + canvasSize.x / static_cast(canvasSize.y); + + ImVec2 imgOrigin, imgSize; + if (caAspectRatio > fbAspectRatio) { + // Framebuffer is narrower than canvas. Match height, + // center horizontally. + const float ww = + std::min(canvasSize.y * fbAspectRatio, canvasSize.x); + imgOrigin.x = canvasOrigin.x + 0.5f * (canvasSize.x - ww); + imgOrigin.y = canvasOrigin.y; + imgSize.x = ww; + imgSize.y = canvasSize.y; + } else { + // Framebuffer is wider than canvas. Match width, center vertically. + const float hh = + std::min(canvasSize.x / fbAspectRatio, canvasSize.y); + imgOrigin.x = canvasOrigin.x; + imgOrigin.y = canvasOrigin.y + 0.5f * (canvasSize.y - hh); + imgSize.x = canvasSize.x; + imgSize.y = hh; + } + + ImGui::SetCursorScreenPos(ImVec2(imgOrigin.x, imgOrigin.y)); + ImGui::Image((void *)(intptr_t)fbTexId, + imgSize, + // Mirror the image. This way, we can just render images + // naturally and they will the right side up both in the + // viewport and when exporting the framebuffer to disk. + ImVec2(1, 1), + ImVec2(0, 0)); + + drawStats(stats, vec2i(imgOrigin.x + 10, imgOrigin.y + 10)); + } + } + + // ------------------------------------------------------------------------- + + inline double toSeconds(const Framebuffer::Stats::Clock::duration &d) + { + using MuS = std::chrono::microseconds; + return std::chrono::duration_cast(d).count() / 1000000.0; + } + + inline double toMilliseconds(const Framebuffer::Stats::Clock::duration &d) + { + using MuS = std::chrono::microseconds; + return std::chrono::duration_cast(d).count() / 1000.0; + } + + void RenderView::drawStats(const Framebuffer::Stats &stats, + const vec2i &origin) const + { + ImDrawList *drawList = ImGui::GetWindowDrawList(); + std::ostringstream os; + os << std::showpoint << std::fixed << std::setprecision(2); + if (stats.frameTime.count() > 0) { + const int fps = static_cast(1.0 / toSeconds(stats.frameTime)); + os << "fps (frame) : " << fps << "\n"; + } + if (stats.renderTime.count() > 0) { + const int fps = static_cast(1.0 / toSeconds(stats.renderTime)); + os << "fps (rendering) : " << fps << "\n"; + os << "rendering : " << std::setw(5) + << toMilliseconds(stats.renderTime) << " ms\n"; + } + if (stats.copyTime.count() > 0) { + os << "framebuffer copy : " << std::setw(5) + << toMilliseconds(stats.copyTime) << " ms\n"; + } + if (stats.tonemapTime.count() > 0) { + os << "tonemapping : " << std::setw(5) + << toMilliseconds(stats.tonemapTime) << " ms\n"; + } + const ImVec2 textOrigin(origin.x, origin.y); + drawList->AddText( + textOrigin, IM_COL32(255, 255, 255, 100), os.str().c_str()); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/RenderView.h b/examples/interactive/RenderView.h new file mode 100644 index 00000000..034ba18f --- /dev/null +++ b/examples/interactive/RenderView.h @@ -0,0 +1,87 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ParameterGui.h" +#include "renderer/Framebuffer.h" + +#include +#include +#include + +namespace openvkl { + namespace examples { + + class Renderer; + class Scheduler; + + /* + * An IMGUI window that interacts with a renderer. + */ + class RenderView + { + using vec2f = rkcommon::math::vec2f; + using vec2i = rkcommon::math::vec2i; + + public: + explicit RenderView(const std::string &name, + std::unique_ptr &&renderer); + + ~RenderView(); + + void drawParameterGui(); + + // Returns false if not visible. + bool draw(); + + const std::string &getName() const + { + return name; + } + + bool &getVisibleFlag() + { + return visible; + } + + bool isVisible() const + { + return static_cast(renderer) && visible; + } + + Renderer const &getRenderer() const + { + assert(renderer); + return *(renderer.get()); + } + Renderer &getRenderer() + { + assert(renderer); + return *(renderer.get()); + } + + private: + void drawBackground(const ImVec2 &canvasOrigin, + const ImVec2 &canvasSize) const; + void drawFramebuffer(const Scheduler &scheduler, + const ImVec2 &canvasOrigin, + const ImVec2 &canvasSize) const; + void drawStats(const Framebuffer::Stats &stats, + const vec2i &origin) const; + + private: + std::string name; + bool visible{true}; + + std::string toolboxName; + bool toolboxVisible{false}; + + std::unique_ptr renderer; + std::unique_ptr parameterGui; + + GLuint fbTexId; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/TransferFunctionWidget.cpp b/examples/interactive/TransferFunctionWidget.cpp new file mode 100644 index 00000000..6970bacb --- /dev/null +++ b/examples/interactive/TransferFunctionWidget.cpp @@ -0,0 +1,598 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "TransferFunctionWidget.h" + +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + namespace help { + + template + int find_idx(const std::vector &A, float p) + { + auto found = std::upper_bound( + A.begin(), A.end(), T(p), [](const T &a, const T &b) { + return a.x < b.x; + }); + return std::distance(A.begin(), found); + } + + float lerp(const float &l, + const float &r, + const float &pl, + const float &pr, + const float &p) + { + const float dl = + std::abs(pr - pl) > 0.0001f ? (p - pl) / (pr - pl) : 0.f; + const float dr = 1.f - dl; + return l * dr + r * dl; + } + + } // namespace help + + TransferFunctionWidget::TransferFunctionWidget( + const range1f &_valueRange, const std::string &_widgetName) + : valueRange(_valueRange), widgetName(_widgetName) + { + loadDefaultMaps(); + + tfnColorPoints = &(tfnsColorPoints[currentMap]); + tfnOpacityPoints = &(tfnsOpacityPoints[currentMap]); + tfnEditable = tfnsEditable[currentMap]; + } + + TransferFunctionWidget::~TransferFunctionWidget() + { +#if defined(VKL_HAVE_IMGUI) + if (tfnPaletteTexture) { + glDeleteTextures(1, &tfnPaletteTexture); + } +#endif // defined(VKL_HAVE_IMGUI) + } + + bool TransferFunctionWidget::updateUI() + { + bool changed = tfnChanged; + +#if defined(VKL_HAVE_IMGUI) + + // set ImGui double click time to 1s, so it also works for slower frame + // rates + ImGuiIO &io = ImGui::GetIO(); + io.MouseDoubleClickTime = 1.f; + + if (tfnChanged) { + updateTfnPaletteTexture(); + tfnChanged = false; + } + + // need a unique ImGui group name per widget + ImGui::PushID(widgetName.c_str()); + + ImGui::Text("Linear Transfer Function"); + + ImGui::Separator(); + std::vector names(tfnsNames.size(), nullptr); + std::transform(tfnsNames.begin(), + tfnsNames.end(), + names.begin(), + [](const std::string &t) { return t.c_str(); }); + + int newMap = currentMap; + if (ImGui::ListBox("Color maps", &newMap, names.data(), names.size())) { + setMap(newMap); + } + + ImGui::Separator(); + + ImGui::Text("Opacity scale"); + ImGui::SameLine(); + if (ImGui::SliderFloat( + "##OpacityScale", &globalOpacityScale, 0.f, 10.f)) { + tfnChanged = true; + } + + ImGui::Separator(); + + if (ImGui::DragFloatRange2("Value range", + &valueRange.lower, + &valueRange.upper, + 0.1f, + -10000.f, + 10000.0f, + "Min: %.7f", + "Max: %.7f")) { + tfnChanged = true; + } + + drawEditor(); + + ImGui::PopID(); + +#endif // defined(VKL_HAVE_IMGUI) + + return changed; + } + + range1f TransferFunctionWidget::getValueRange() const + { + return valueRange; + } + + void TransferFunctionWidget::setValueRange(const range1f &range) + { + valueRange = range; + } + + std::vector TransferFunctionWidget::getSampledColorsAndOpacities( + int numSamples) const + { + std::vector sampledColorsAndOpacities; + + const float dx = 1.f / (numSamples - 1); + + for (int i = 0; i < numSamples; i++) { + sampledColorsAndOpacities.push_back( + vec4f(interpolateColor(*tfnColorPoints, i * dx), + interpolateOpacity(*tfnOpacityPoints, i * dx) * + globalOpacityScale)); + } + + return sampledColorsAndOpacities; + } + + void TransferFunctionWidget::loadDefaultMaps() + { + // same opacities for all maps + std::vector opacities; + + opacities.emplace_back(0.f, 0.f); + opacities.emplace_back(1.f, 1.f); + + // Jet + std::vector colors; + + colors.emplace_back(0.0f, 0.f, 0.f, 1.f); + colors.emplace_back(0.3f, 0.f, 1.f, 1.f); + colors.emplace_back(0.6f, 1.f, 1.f, 0.f); + colors.emplace_back(1.0f, 1.f, 0.f, 0.f); + + tfnsColorPoints.push_back(colors); + tfnsOpacityPoints.push_back(opacities); + + tfnsEditable.push_back(true); + tfnsNames.push_back("Jet"); + + // Ice Fire + colors.clear(); + + const float spacing = 1.f / 16; + + colors.emplace_back(0 * spacing, 0, 0, 0); + colors.emplace_back(1 * spacing, 0, 0.120394, 0.302678); + colors.emplace_back(2 * spacing, 0, 0.216587, 0.524575); + colors.emplace_back(3 * spacing, 0.0552529, 0.345022, 0.659495); + colors.emplace_back(4 * spacing, 0.128054, 0.492592, 0.720287); + colors.emplace_back(5 * spacing, 0.188952, 0.641306, 0.792096); + colors.emplace_back(6 * spacing, 0.327672, 0.784939, 0.873426); + colors.emplace_back(7 * spacing, 0.60824, 0.892164, 0.935546); + colors.emplace_back(8 * spacing, 0.881376, 0.912184, 0.818097); + colors.emplace_back(9 * spacing, 0.9514, 0.835615, 0.449271); + colors.emplace_back(10 * spacing, 0.904479, 0.690486, 0); + colors.emplace_back(11 * spacing, 0.854063, 0.510857, 0); + colors.emplace_back(12 * spacing, 0.777096, 0.330175, 0.000885023); + colors.emplace_back(13 * spacing, 0.672862, 0.139086, 0.00270085); + colors.emplace_back(14 * spacing, 0.508812, 0, 0); + colors.emplace_back(15 * spacing, 0.299413, 0.000366217, 0.000549325); + + colors.emplace_back(1.f, 0.0157473, 0.00332647, 0); + + tfnsColorPoints.push_back(colors); + tfnsOpacityPoints.push_back(opacities); + + tfnsEditable.push_back(true); + tfnsNames.push_back("Ice Fire"); + + // Grayscale + colors.clear(); + + colors.emplace_back(0.f, 1.f, 1.f, 1.f); + colors.emplace_back(1.f, 1.f, 1.f, 1.f); + + tfnsColorPoints.push_back(colors); + tfnsOpacityPoints.push_back(opacities); + + tfnsEditable.push_back(true); + tfnsNames.push_back("Grayscale"); + }; + + void TransferFunctionWidget::setMap(int selection) + { + if (currentMap != selection) { + currentMap = selection; + // Remember to update other constructors as well + tfnColorPoints = &(tfnsColorPoints[selection]); +#if 1 // NOTE(jda) - this will use the first tf's opacities for all color maps + tfnOpacityPoints = &(tfnsOpacityPoints[selection]); +#endif + tfnEditable = tfnsEditable[selection]; + tfnChanged = true; + } + } + + vec3f TransferFunctionWidget::interpolateColor( + const std::vector &controlPoints, float x) const + { + auto first = controlPoints.front(); + if (x <= first.x) + return vec3f(first.y, first.z, first.w); + + for (uint32_t i = 1; i < controlPoints.size(); i++) { + auto current = controlPoints[i]; + auto previous = controlPoints[i - 1]; + if (x <= current.x) { + const float t = (x - previous.x) / (current.x - previous.x); + return (1.0 - t) * vec3f(previous.y, previous.z, previous.w) + + t * vec3f(current.y, current.z, current.w); + } + } + + auto last = controlPoints.back(); + return vec3f(last.x, last.y, last.z); + } + + float TransferFunctionWidget::interpolateOpacity( + const std::vector &controlPoints, float x) const + + { + auto first = controlPoints.front(); + if (x <= first.x) + return first.y; + + for (uint32_t i = 1; i < controlPoints.size(); i++) { + auto current = controlPoints[i]; + auto previous = controlPoints[i - 1]; + if (x <= current.x) { + const float t = (x - previous.x) / (current.x - previous.x); + return (1.0 - t) * previous.y + t * current.y; + } + } + + auto last = controlPoints.back(); + return last.y; + } + + void TransferFunctionWidget::updateTfnPaletteTexture() + { +#if defined(VKL_HAVE_IMGUI) + const size_t textureWidth = 256, textureHeight = 1; + + // backup currently bound texture + GLint prevBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevBinding); + + // create transfer function palette texture if it doesn't exist + if (!tfnPaletteTexture) { + glGenTextures(1, &tfnPaletteTexture); + glBindTexture(GL_TEXTURE_2D, tfnPaletteTexture); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA8, + textureWidth, + textureHeight, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + // sample the palette then upload the data + std::vector palette = getSampledColorsAndOpacities(textureWidth); + + // save palette to texture + glBindTexture(GL_TEXTURE_2D, tfnPaletteTexture); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGB, + textureWidth, + textureHeight, + 0, + GL_RGBA, + GL_FLOAT, + static_cast(palette.data())); + + // restore previously bound texture + if (prevBinding) { + glBindTexture(GL_TEXTURE_2D, prevBinding); + } +#endif //defined(VKL_HAVE_IMGUI) + } + + void TransferFunctionWidget::drawEditor() + { +#if defined(VKL_HAVE_IMGUI) + // only God and me know what do they do ... + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + float canvas_x = ImGui::GetCursorScreenPos().x; + float canvas_y = ImGui::GetCursorScreenPos().y; + float canvas_avail_x = ImGui::GetContentRegionAvail().x; + float canvas_avail_y = ImGui::GetContentRegionAvail().y; + const float mouse_x = ImGui::GetMousePos().x; + const float mouse_y = ImGui::GetMousePos().y; + const float scroll_x = ImGui::GetScrollX(); + const float scroll_y = ImGui::GetScrollY(); + const float margin = 10.f; + const float width = canvas_avail_x - 2.f * margin; + const float height = 260.f; + const float color_len = 9.f; + const float opacity_len = 7.f; + + // draw preview texture + ImGui::SetCursorScreenPos(ImVec2(canvas_x + margin, canvas_y)); + ImGui::Image(reinterpret_cast(tfnPaletteTexture), + ImVec2(width, height)); + + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); + for (int i = 0; i < tfnOpacityPoints->size() - 1; ++i) { + std::vector polyline; + polyline.emplace_back( + canvas_x + margin + (*tfnOpacityPoints)[i].x * width, + canvas_y + height); + polyline.emplace_back( + canvas_x + margin + (*tfnOpacityPoints)[i].x * width, + canvas_y + height - (*tfnOpacityPoints)[i].y * height); + polyline.emplace_back( + canvas_x + margin + (*tfnOpacityPoints)[i + 1].x * width + 1, + canvas_y + height - (*tfnOpacityPoints)[i + 1].y * height); + polyline.emplace_back( + canvas_x + margin + (*tfnOpacityPoints)[i + 1].x * width + 1, + canvas_y + height); + draw_list->AddConvexPolyFilled( + polyline.data(), polyline.size(), 0xFFD8D8D8); + } + canvas_y += height + margin; + canvas_avail_y -= height + margin; + + // draw color control points + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); + + if (tfnEditable) { + // draw circle background + draw_list->AddRectFilled(ImVec2(canvas_x + margin, canvas_y - margin), + ImVec2(canvas_x + margin + width, + canvas_y - margin + 2.5 * color_len), + 0xFF474646); + + // draw circles + for (int i = tfnColorPoints->size() - 1; i >= 0; --i) { + const ImVec2 pos(canvas_x + width * (*tfnColorPoints)[i].x + margin, + canvas_y); + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); + + // white background + draw_list->AddTriangleFilled(ImVec2(pos.x - 0.5f * color_len, pos.y), + ImVec2(pos.x + 0.5f * color_len, pos.y), + ImVec2(pos.x, pos.y - color_len), + 0xFFD8D8D8); + draw_list->AddCircleFilled( + ImVec2(pos.x, pos.y + 0.5f * color_len), color_len, 0xFFD8D8D8); + + // draw picker + ImVec4 picked_color = ImColor((*tfnColorPoints)[i].y, + (*tfnColorPoints)[i].z, + (*tfnColorPoints)[i].w, + 1.f); + ImGui::SetCursorScreenPos( + ImVec2(pos.x - color_len, pos.y + 1.5f * color_len)); + if (ImGui::ColorEdit4(("##ColorPicker" + std::to_string(i)).c_str(), + (float *)&picked_color, + ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoInputs | + ImGuiColorEditFlags_NoLabel | + ImGuiColorEditFlags_AlphaPreview | + ImGuiColorEditFlags_NoOptions | + ImGuiColorEditFlags_NoTooltip)) { + (*tfnColorPoints)[i].y = picked_color.x; + (*tfnColorPoints)[i].z = picked_color.y; + (*tfnColorPoints)[i].w = picked_color.z; + tfnChanged = true; + } + if (ImGui::IsItemHovered()) { + // convert float color to char + int cr = static_cast(picked_color.x * 255); + int cg = static_cast(picked_color.y * 255); + int cb = static_cast(picked_color.z * 255); + + // setup tooltip + ImGui::BeginTooltip(); + ImVec2 sz( + ImGui::GetFontSize() * 4 + ImGui::GetStyle().FramePadding.y * 2, + ImGui::GetFontSize() * 4 + + ImGui::GetStyle().FramePadding.y * 2); + ImGui::ColorButton( + "##PreviewColor", + picked_color, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview, + sz); + ImGui::SameLine(); + ImGui::Text( + "Left click to edit\n" + "HEX: #%02X%02X%02X\n" + "RGB: [%3d,%3d,%3d]\n(%.2f, %.2f, %.2f)", + cr, + cg, + cb, + cr, + cg, + cb, + picked_color.x, + picked_color.y, + picked_color.z); + ImGui::EndTooltip(); + } + } + for (int i = 0; i < tfnColorPoints->size(); ++i) { + const ImVec2 pos(canvas_x + width * (*tfnColorPoints)[i].x + margin, + canvas_y); + + // draw button + ImGui::SetCursorScreenPos( + ImVec2(pos.x - color_len, pos.y - 0.5 * color_len)); + ImGui::InvisibleButton( + ("##ColorControl-" + std::to_string(i)).c_str(), + ImVec2(2.f * color_len, 2.f * color_len)); + + // dark highlight + ImGui::SetCursorScreenPos(ImVec2(pos.x - color_len, pos.y)); + draw_list->AddCircleFilled( + ImVec2(pos.x, pos.y + 0.5f * color_len), + 0.5f * color_len, + ImGui::IsItemHovered() ? 0xFF051C33 : 0xFFBCBCBC); + + // delete color point + if (ImGui::IsMouseDoubleClicked(1) && ImGui::IsItemHovered()) { + if (i > 0 && i < tfnColorPoints->size() - 1) { + tfnColorPoints->erase(tfnColorPoints->begin() + i); + tfnChanged = true; + } + } + + // drag color control point + else if (ImGui::IsItemActive()) { + ImVec2 delta = ImGui::GetIO().MouseDelta; + if (i > 0 && i < tfnColorPoints->size() - 1) { + (*tfnColorPoints)[i].x += delta.x / width; + (*tfnColorPoints)[i].x = clamp((*tfnColorPoints)[i].x, + (*tfnColorPoints)[i - 1].x, + (*tfnColorPoints)[i + 1].x); + } + + tfnChanged = true; + } + } + } + + // draw opacity control points + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); + { + // draw circles + for (int i = 0; i < tfnOpacityPoints->size(); ++i) { + const ImVec2 pos( + canvas_x + width * (*tfnOpacityPoints)[i].x + margin, + canvas_y - height * (*tfnOpacityPoints)[i].y - margin); + ImGui::SetCursorScreenPos( + ImVec2(pos.x - opacity_len, pos.y - opacity_len)); + ImGui::InvisibleButton( + ("##OpacityControl-" + std::to_string(i)).c_str(), + ImVec2(2.f * opacity_len, 2.f * opacity_len)); + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); + + // dark bounding box + draw_list->AddCircleFilled(pos, opacity_len, 0xFF565656); + + // white background + draw_list->AddCircleFilled(pos, 0.8f * opacity_len, 0xFFD8D8D8); + + // highlight + draw_list->AddCircleFilled( + pos, + 0.6f * opacity_len, + ImGui::IsItemHovered() ? 0xFF051c33 : 0xFFD8D8D8); + + // setup interaction + + // delete opacity point + if (ImGui::IsMouseDoubleClicked(1) && ImGui::IsItemHovered()) { + if (i > 0 && i < tfnOpacityPoints->size() - 1) { + tfnOpacityPoints->erase(tfnOpacityPoints->begin() + i); + tfnChanged = true; + } + } else if (ImGui::IsItemActive()) { + ImVec2 delta = ImGui::GetIO().MouseDelta; + (*tfnOpacityPoints)[i].y -= delta.y / height; + (*tfnOpacityPoints)[i].y = + clamp((*tfnOpacityPoints)[i].y, 0.0f, 1.0f); + if (i > 0 && i < tfnOpacityPoints->size() - 1) { + (*tfnOpacityPoints)[i].x += delta.x / width; + (*tfnOpacityPoints)[i].x = clamp((*tfnOpacityPoints)[i].x, + (*tfnOpacityPoints)[i - 1].x, + (*tfnOpacityPoints)[i + 1].x); + } + tfnChanged = true; + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Double right click button to delete point\n" + "Left click and drag to move point"); + } + } + } + + // draw background interaction + ImGui::SetCursorScreenPos(ImVec2(canvas_x + margin, canvas_y - margin)); + ImGui::InvisibleButton("##tfn_palette_color", + ImVec2(width, 2.5 * color_len)); + + // add color point + if (tfnEditable && ImGui::IsMouseDoubleClicked(0) && + ImGui::IsItemHovered()) { + const float p = clamp( + (mouse_x - canvas_x - margin - scroll_x) / (float)width, 0.f, 1.f); + const int ir = help::find_idx(*tfnColorPoints, p); + const int il = ir - 1; + const float pr = (*tfnColorPoints)[ir].x; + const float pl = (*tfnColorPoints)[il].x; + const float r = help::lerp( + (*tfnColorPoints)[il].y, (*tfnColorPoints)[ir].y, pl, pr, p); + const float g = help::lerp( + (*tfnColorPoints)[il].z, (*tfnColorPoints)[ir].z, pl, pr, p); + const float b = help::lerp( + (*tfnColorPoints)[il].w, (*tfnColorPoints)[ir].w, pl, pr, p); + ColorPoint pt(p, r, g, b); + tfnColorPoints->insert(tfnColorPoints->begin() + ir, pt); + tfnChanged = true; + } + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Double left click to add new color point"); + } + + // draw background interaction + ImGui::SetCursorScreenPos( + ImVec2(canvas_x + margin, canvas_y - height - margin)); + ImGui::InvisibleButton("##tfn_palette_opacity", ImVec2(width, height)); + + // add opacity point + if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { + const float x = clamp( + (mouse_x - canvas_x - margin - scroll_x) / (float)width, 0.f, 1.f); + const float y = + clamp(-(mouse_y - canvas_y + margin - scroll_y) / (float)height, + 0.f, + 1.f); + const int idx = help::find_idx(*tfnOpacityPoints, x); + OpacityPoint pt(x, y); + tfnOpacityPoints->insert(tfnOpacityPoints->begin() + idx, pt); + tfnChanged = true; + } + + // update cursors + canvas_y += 4.f * color_len + margin; + canvas_avail_y -= 4.f * color_len + margin; + + ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); +#endif //defined(VKL_HAVE_IMGUI) + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/TransferFunctionWidget.h b/examples/interactive/TransferFunctionWidget.h new file mode 100644 index 00000000..0745e107 --- /dev/null +++ b/examples/interactive/TransferFunctionWidget.h @@ -0,0 +1,87 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include // Push/PopDisabled + +#include +#include + +#include +#include +#include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + using ColorPoint = vec4f; + using OpacityPoint = vec2f; + + class TransferFunctionWidget + { + public: + TransferFunctionWidget( + const range1f &valueRange = range1f(-1.f, 1.f), + const std::string &widgetName = "Transfer Function"); + ~TransferFunctionWidget(); + + // update UI and process any UI events + // Returns true if anything was changed. + bool updateUI(); + + // getters for current transfer function data + range1f getValueRange() const; + void setValueRange(const range1f &range); + std::vector getSampledColorsAndOpacities(int numSamples = 256) + const; + + private: + void loadDefaultMaps(); + void setMap(int); + + vec3f interpolateColor(const std::vector &controlPoints, + float x) const; + + float interpolateOpacity(const std::vector &controlPoints, + float x) const; + + void updateTfnPaletteTexture(); + + void drawEditor(); + + // all available transfer functions + std::vector tfnsNames; + std::vector> tfnsColorPoints; + std::vector> tfnsOpacityPoints; + std::vector tfnsEditable; + + // properties of currently selected transfer function + int currentMap{0}; + std::vector *tfnColorPoints{nullptr}; + std::vector *tfnOpacityPoints{nullptr}; + bool tfnEditable{true}; + + // flag indicating transfer function has changed in UI + bool tfnChanged{true}; + + // scaling factor for generated opacities + float globalOpacityScale{1.f}; + + // domain (value range) of transfer function + range1f valueRange{-1.f, 1.f}; + + // texture for displaying transfer function color palette + GLuint tfnPaletteTexture{0}; + + // widget name (use different names to support multiple concurrent + // widgets) + std::string widgetName; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/imgui-1.81/.editorconfig b/examples/interactive/imgui-1.81/.editorconfig deleted file mode 100644 index 284ba13f..00000000 --- a/examples/interactive/imgui-1.81/.editorconfig +++ /dev/null @@ -1,22 +0,0 @@ -# See http://editorconfig.org to read about the EditorConfig format. -# - Automatically supported by VS2017+ and most common IDE or text editors. -# - For older VS2010 to VS2015, install https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig - -# top-most EditorConfig file -root = true - -# Default settings: -# Use 4 spaces as indentation -[*] -indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true - -[imstb_*] -indent_size = 3 -trim_trailing_whitespace = false - -[Makefile] -indent_style = tab -indent_size = 4 diff --git a/examples/interactive/imgui-1.81/examples/README.txt b/examples/interactive/imgui-1.81/examples/README.txt deleted file mode 100644 index ed16dd9f..00000000 --- a/examples/interactive/imgui-1.81/examples/README.txt +++ /dev/null @@ -1 +0,0 @@ -See EXAMPLES and BACKENDS files in the docs/ folder. diff --git a/examples/interactive/imgui-1.81/misc/natvis/README.txt b/examples/interactive/imgui-1.81/misc/natvis/README.txt deleted file mode 100644 index 1219db45..00000000 --- a/examples/interactive/imgui-1.81/misc/natvis/README.txt +++ /dev/null @@ -1,4 +0,0 @@ - -Natvis file to describe dear imgui types in the Visual Studio debugger. -With this, types like ImVector<> will be displayed nicely in the debugger. -You can include this file a Visual Studio project file, or install it in Visual Studio folder. diff --git a/examples/interactive/imgui-1.83/.editorconfig b/examples/interactive/imgui-1.83/.editorconfig new file mode 100644 index 00000000..c6dc600a --- /dev/null +++ b/examples/interactive/imgui-1.83/.editorconfig @@ -0,0 +1,24 @@ +# See http://editorconfig.org to read about the EditorConfig format. +# - In theory automatically supported by VS2017+ and most common IDE or text editors. +# - In practice VS2019 stills gets trailing whitespaces wrong :( +# - Suggest install to trim whitespaces: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer +# - Alternative for older VS2010 to VS2015: https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig + +# top-most EditorConfig file +root = true + +# Default settings: +# Use 4 spaces as indentation +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[imstb_*] +indent_size = 3 +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab +indent_size = 4 diff --git a/examples/interactive/imgui-1.81/.gitattributes b/examples/interactive/imgui-1.83/.gitattributes similarity index 100% rename from examples/interactive/imgui-1.81/.gitattributes rename to examples/interactive/imgui-1.83/.gitattributes diff --git a/examples/interactive/imgui-1.81/.github/FUNDING.yml b/examples/interactive/imgui-1.83/.github/FUNDING.yml similarity index 100% rename from examples/interactive/imgui-1.81/.github/FUNDING.yml rename to examples/interactive/imgui-1.83/.github/FUNDING.yml diff --git a/examples/interactive/imgui-1.81/.github/issue_template.md b/examples/interactive/imgui-1.83/.github/issue_template.md similarity index 95% rename from examples/interactive/imgui-1.81/.github/issue_template.md rename to examples/interactive/imgui-1.83/.github/issue_template.md index 3812ad0b..4fe6119c 100644 --- a/examples/interactive/imgui-1.81/.github/issue_template.md +++ b/examples/interactive/imgui-1.83/.github/issue_template.md @@ -4,7 +4,7 @@ 2. PLEASE CAREFULLY READ: [Issue Submitting Guidelines](https://github.com/ocornut/imgui/issues/2261) -3. FOR FIRST-TIME USERS ISSUES COMPILING/LINKING/RUNNING/LOADING FONTS, please use the [Discord server](http://discord.dearimgui.org). +3. FOR FIRST-TIME USERS ISSUES COMPILING/LINKING/RUNNING/LOADING FONTS, please use [GitHub Discussions](https://github.com/ocornut/imgui/discussions). 4. PLEASE MAKE SURE that you have: read the FAQ; explored the contents of `ShowDemoWindow()` including the Examples menu; searched among Issues; used your IDE to search for keywords in all sources and text files; and read the link provided in (1) (2). diff --git a/examples/interactive/imgui-1.81/.github/pull_request_template.md b/examples/interactive/imgui-1.83/.github/pull_request_template.md similarity index 100% rename from examples/interactive/imgui-1.81/.github/pull_request_template.md rename to examples/interactive/imgui-1.83/.github/pull_request_template.md diff --git a/examples/interactive/imgui-1.81/.github/workflows/build.yml b/examples/interactive/imgui-1.83/.github/workflows/build.yml similarity index 90% rename from examples/interactive/imgui-1.81/.github/workflows/build.yml rename to examples/interactive/imgui-1.83/.github/workflows/build.yml index 352cdcb3..09b09e71 100644 --- a/examples/interactive/imgui-1.81/.github/workflows/build.yml +++ b/examples/interactive/imgui-1.83/.github/workflows/build.yml @@ -3,8 +3,16 @@ name: build on: push: pull_request: - schedule: - - cron: '0 9 * * *' + workflow_run: + # Use a workflow as a trigger of scheduled builds. Forked repositories can disable scheduled builds by disabling + # "scheduled" workflow, while maintaining ability to perform local CI builds. + workflows: + - scheduled + branches: + - master + - docking + types: + - requested jobs: Windows: @@ -60,7 +68,7 @@ jobs: #include "examples/example_null/main.cpp" EOF - g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp + g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32 - name: Build example_null (with IMGUI_DISABLE_WIN32_FUNCTIONS) shell: bash @@ -73,7 +81,7 @@ jobs: #include "examples/example_null/main.cpp" EOF - g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp + g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32 - name: Build example_null (as DLL) shell: cmd @@ -98,22 +106,22 @@ jobs: - name: Build Win32 example_glfw_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build Win32 example_glfw_vulkan shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build Win32 example_sdl_vulkan shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build Win32 example_sdl_opengl2 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build Win32 example_sdl_opengl3 shell: cmd @@ -122,7 +130,7 @@ jobs: - name: Build Win32 example_sdl_directx11 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_directx11/example_sdl_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build Win32 example_win32_directx9 shell: cmd @@ -135,12 +143,12 @@ jobs: - name: Build Win32 example_win32_directx11 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_glfw_opengl2 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_glfw_opengl3 shell: cmd @@ -153,17 +161,17 @@ jobs: - name: Build x64 example_sdl_vulkan shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_sdl_opengl2 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_sdl_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_sdl_directx11 shell: cmd @@ -172,17 +180,17 @@ jobs: - name: Build x64 example_win32_directx9 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx9/example_win32_directx9.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_win32_directx10 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx10/example_win32_directx10.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_win32_directx11 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release' - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build x64 example_win32_directx12 shell: cmd @@ -196,7 +204,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install -y libglfw3-dev libsdl2-dev gcc-multilib g++-multilib libfreetype6-dev + sudo apt-get install -y libglfw3-dev libsdl2-dev gcc-multilib g++-multilib libfreetype6-dev libvulkan-dev - name: Build example_null (extra warnings, gcc 32-bit) run: | @@ -246,10 +254,11 @@ jobs: EOF g++ -I. -Wall -Wformat -o example_single_file example_single_file.cpp - - name: Build example_null (with large ImDrawIdx) + - name: Build example_null (with large ImDrawIdx + pointer ImTextureID) run: | cat > example_single_file.cpp <<'EOF' + #define ImTextureID void* #define ImDrawIdx unsigned int #define IMGUI_IMPLEMENTATION #include "misc/single_file/imgui_single_file.h" @@ -344,17 +353,20 @@ jobs: - name: Build example_glfw_opengl3 run: make -C examples/example_glfw_opengl3 - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build example_sdl_opengl2 run: make -C examples/example_sdl_opengl2 - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build example_sdl_opengl3 run: make -C examples/example_sdl_opengl3 + - name: Build with IMGUI_IMPL_VULKAN_NO_PROTOTYPES + run: g++ -c -I. -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp + MacOS: - runs-on: macOS-latest + runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -392,7 +404,7 @@ jobs: - name: Build example_glfw_opengl3 run: make -C examples/example_glfw_opengl3 - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build example_glfw_metal run: make -C examples/example_glfw_metal @@ -402,7 +414,7 @@ jobs: - name: Build example_sdl_opengl2 run: make -C examples/example_sdl_opengl2 - if: github.event_name == 'schedule' + if: github.event_name == 'workflow_run' - name: Build example_sdl_opengl3 run: make -C examples/example_sdl_opengl3 @@ -414,7 +426,7 @@ jobs: run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 iOS: - runs-on: macOS-latest + runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -450,17 +462,27 @@ jobs: popd make -C examples/example_emscripten_wgpu + Android: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + + - name: Build example_android_opengl3 + run: | + cd examples/example_android_opengl3/android + gradle assembleDebug + Discord-CI: runs-on: ubuntu-18.04 if: always() - needs: [Windows, Linux, MacOS, iOS, Emscripten] + needs: [Windows, Linux, MacOS, iOS, Emscripten, Android] steps: - uses: dearimgui/github_discord_notifier@latest with: discord-webhook: ${{ secrets.DISCORD_CI_WEBHOOK }} github-token: ${{ github.token }} action-task: discord-jobs - discord-filter: "'{{ github.branch }}'.match(/master|docking|tables/g) != null && '{{ run.conclusion }}' != '{{ last_run.conclusion }}'" + discord-filter: "'{{ github.branch }}'.match(/master|docking/g) != null && '{{ run.conclusion }}' != '{{ last_run.conclusion }}'" discord-username: GitHub Actions discord-job-new-failure-message: '' discord-job-fixed-failure-message: '' diff --git a/examples/interactive/imgui-1.83/.github/workflows/scheduled.yml b/examples/interactive/imgui-1.83/.github/workflows/scheduled.yml new file mode 100644 index 00000000..2a08578f --- /dev/null +++ b/examples/interactive/imgui-1.83/.github/workflows/scheduled.yml @@ -0,0 +1,15 @@ +# +# This is a dummy workflow used to trigger scheduled builds. Forked repositories most likely should disable this +# workflow to avoid daily builds of inactive repositories. +# +name: scheduled + +on: + schedule: + - cron: '0 9 * * *' + +jobs: + scheduled: + runs-on: ubuntu-latest + steps: + - run: exit 0 diff --git a/examples/interactive/imgui-1.81/.github/workflows/static-analysis.yml b/examples/interactive/imgui-1.83/.github/workflows/static-analysis.yml similarity index 89% rename from examples/interactive/imgui-1.81/.github/workflows/static-analysis.yml rename to examples/interactive/imgui-1.83/.github/workflows/static-analysis.yml index 7a9b573e..f3e6b2d1 100644 --- a/examples/interactive/imgui-1.81/.github/workflows/static-analysis.yml +++ b/examples/interactive/imgui-1.83/.github/workflows/static-analysis.yml @@ -1,8 +1,12 @@ name: static-analysis on: - push: {} - pull_request: {} + workflow_run: + # Perform static analysis together with build workflow. Build triggers of "build" workflow do not need to be repeated here. + workflows: + - build + types: + - requested jobs: PVS-Studio: @@ -39,7 +43,7 @@ jobs: cd examples/example_null pvs-studio-analyzer trace -- make WITH_EXTRA_WARNINGS=1 pvs-studio-analyzer analyze -e ../../imstb_rectpack.h -e ../../imstb_textedit.h -e ../../imstb_truetype.h -l ../../pvs-studio.lic -o pvs-studio.log - plog-converter -a 'GA:1,2;OP:1' -t errorfile -w pvs-studio.log + plog-converter -a 'GA:1,2;OP:1' -d V1071 -t errorfile -w pvs-studio.log Discord-CI: runs-on: ubuntu-18.04 @@ -51,7 +55,7 @@ jobs: discord-webhook: ${{ secrets.DISCORD_CI_WEBHOOK }} github-token: ${{ github.token }} action-task: discord-jobs - discord-filter: "'{{ github.branch }}'.match(/master|docking|tables/g) != null && '{{ run.conclusion }}' != '{{ last_run.conclusion }}'" + discord-filter: "'{{ github.branch }}'.match(/master|docking/g) != null && '{{ run.conclusion }}' != '{{ last_run.conclusion }}'" discord-username: GitHub Actions discord-job-new-failure-message: '' discord-job-fixed-failure-message: '' diff --git a/examples/interactive/imgui-1.81/.gitignore b/examples/interactive/imgui-1.83/.gitignore similarity index 95% rename from examples/interactive/imgui-1.81/.gitignore rename to examples/interactive/imgui-1.83/.gitignore index 8d720199..86bed609 100644 --- a/examples/interactive/imgui-1.81/.gitignore +++ b/examples/interactive/imgui-1.83/.gitignore @@ -26,6 +26,9 @@ ipch *.VC.db *.VC.VC.opendb +## Commonly used CMake directories +/build*/ + ## Xcode artifacts project.xcworkspace xcuserdata diff --git a/examples/interactive/imgui-1.81/LICENSE.txt b/examples/interactive/imgui-1.83/LICENSE.txt similarity index 100% rename from examples/interactive/imgui-1.81/LICENSE.txt rename to examples/interactive/imgui-1.83/LICENSE.txt diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.cpp similarity index 78% rename from examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.cpp index db608e02..6b1e81f7 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.cpp @@ -9,12 +9,16 @@ // [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually. // [ ] Platform: Missing gamepad support. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: Renderer: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: Change blending equation to preserve alpha in output buffer. // 2020-08-10: Inputs: Fixed horizontal mouse wheel direction. // 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. // 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. @@ -50,13 +54,23 @@ #pragma warning (disable: 4127) // condition expression is constant #endif -// Data -static ALLEGRO_DISPLAY* g_Display = NULL; -static ALLEGRO_BITMAP* g_Texture = NULL; -static double g_Time = 0.0; -static ALLEGRO_MOUSE_CURSOR* g_MouseCursorInvisible = NULL; -static ALLEGRO_VERTEX_DECL* g_VertexDecl = NULL; -static char* g_ClipboardTextData = NULL; +// Allegro Data +struct ImGui_ImplAllegro5_Data +{ + ALLEGRO_DISPLAY* Display; + ALLEGRO_BITMAP* Texture; + double Time; + ALLEGRO_MOUSE_CURSOR* MouseCursorInvisible; + ALLEGRO_VERTEX_DECL* VertexDecl; + char* ClipboardTextData; + + ImGui_ImplAllegro5_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in this backend. +static ImGui_ImplAllegro5_Data* ImGui_ImplAllegro5_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplAllegro5_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; } struct ImDrawVertAllegro { @@ -68,7 +82,7 @@ struct ImDrawVertAllegro static void ImGui_ImplAllegro5_SetupRenderState(ImDrawData* draw_data) { // Setup blending - al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA); + al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA); // Setup orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). @@ -93,6 +107,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) return; // Backup Allegro state that will be modified + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ALLEGRO_TRANSFORM last_transform = *al_get_current_transform(); ALLEGRO_TRANSFORM last_projection_transform = *al_get_current_projection_transform(); int last_clip_x, last_clip_y, last_clip_w, last_clip_h; @@ -156,9 +171,9 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) else { // Draw - ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->TextureId; + ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->GetTexID(); al_set_clipping_rectangle(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y, pcmd->ClipRect.z - pcmd->ClipRect.x, pcmd->ClipRect.w - pcmd->ClipRect.y); - al_draw_prim(&vertices[0], g_VertexDecl, texture, idx_offset, idx_offset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST); + al_draw_prim(&vertices[0], bd->VertexDecl, texture, idx_offset, idx_offset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST); } idx_offset += pcmd->ElemCount; } @@ -174,6 +189,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) bool ImGui_ImplAllegro5_CreateDeviceObjects() { // Build texture atlas + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; @@ -207,12 +223,12 @@ bool ImGui_ImplAllegro5_CreateDeviceObjects() // Store our identifier io.Fonts->SetTexID((void*)cloned_img); - g_Texture = cloned_img; + bd->Texture = cloned_img; // Create an invisible mouse cursor // Because al_hide_mouse_cursor() seems to mess up with the actual inputs.. ALLEGRO_BITMAP* mouse_cursor = al_create_bitmap(8, 8); - g_MouseCursorInvisible = al_create_mouse_cursor(mouse_cursor, 0, 0); + bd->MouseCursorInvisible = al_create_mouse_cursor(mouse_cursor, 0, 0); al_destroy_bitmap(mouse_cursor); return true; @@ -220,43 +236,50 @@ bool ImGui_ImplAllegro5_CreateDeviceObjects() void ImGui_ImplAllegro5_InvalidateDeviceObjects() { - if (g_Texture) + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); + if (bd->Texture) { - ImGuiIO& io = ImGui::GetIO(); io.Fonts->SetTexID(NULL); - al_destroy_bitmap(g_Texture); - g_Texture = NULL; + al_destroy_bitmap(bd->Texture); + bd->Texture = NULL; } - if (g_MouseCursorInvisible) + if (bd->MouseCursorInvisible) { - al_destroy_mouse_cursor(g_MouseCursorInvisible); - g_MouseCursorInvisible = NULL; + al_destroy_mouse_cursor(bd->MouseCursorInvisible); + bd->MouseCursorInvisible = NULL; } } #if ALLEGRO_HAS_CLIPBOARD static const char* ImGui_ImplAllegro5_GetClipboardText(void*) { - if (g_ClipboardTextData) - al_free(g_ClipboardTextData); - g_ClipboardTextData = al_get_clipboard_text(g_Display); - return g_ClipboardTextData; + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); + if (bd->ClipboardTextData) + al_free(bd->ClipboardTextData); + bd->ClipboardTextData = al_get_clipboard_text(bd->Display); + return bd->ClipboardTextData; } static void ImGui_ImplAllegro5_SetClipboardText(void*, const char* text) { - al_set_clipboard_text(g_Display, text); + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); + al_set_clipboard_text(bd->Display, text); } #endif bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) { - g_Display = display; + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); // Setup backend capabilities flags - ImGuiIO& io = ImGui::GetIO(); - io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + ImGui_ImplAllegro5_Data* bd = IM_NEW(ImGui_ImplAllegro5_Data)(); + io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + + bd->Display = display; // Create custom vertex declaration. // Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 floats. @@ -268,7 +291,7 @@ bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) { ALLEGRO_PRIM_COLOR_ATTR, 0, IM_OFFSETOF(ImDrawVertAllegro, col) }, { 0, 0, 0 } }; - g_VertexDecl = al_create_vertex_decl(elems, sizeof(ImDrawVertAllegro)); + bd->VertexDecl = al_create_vertex_decl(elems, sizeof(ImDrawVertAllegro)); io.KeyMap[ImGuiKey_Tab] = ALLEGRO_KEY_TAB; io.KeyMap[ImGuiKey_LeftArrow] = ALLEGRO_KEY_LEFT; @@ -305,18 +328,18 @@ bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) void ImGui_ImplAllegro5_Shutdown() { - ImGui_ImplAllegro5_InvalidateDeviceObjects(); - - g_Display = NULL; - g_Time = 0.0; - - if (g_VertexDecl) - al_destroy_vertex_decl(g_VertexDecl); - g_VertexDecl = NULL; + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); - if (g_ClipboardTextData) - al_free(g_ClipboardTextData); - g_ClipboardTextData = NULL; + ImGui_ImplAllegro5_InvalidateDeviceObjects(); + if (bd->VertexDecl) + al_destroy_vertex_decl(bd->VertexDecl); + if (bd->ClipboardTextData) + al_free(bd->ClipboardTextData); + + io.BackendPlatformUserData = NULL; + io.BackendPlatformName = io.BackendRendererName = NULL; + IM_DELETE(bd); } // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. @@ -326,11 +349,12 @@ void ImGui_ImplAllegro5_Shutdown() bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* ev) { ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); switch (ev->type) { case ALLEGRO_EVENT_MOUSE_AXES: - if (ev->mouse.display == g_Display) + if (ev->mouse.display == bd->Display) { io.MouseWheel += ev->mouse.dz; io.MouseWheelH -= ev->mouse.dw; @@ -339,31 +363,31 @@ bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* ev) return true; case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: case ALLEGRO_EVENT_MOUSE_BUTTON_UP: - if (ev->mouse.display == g_Display && ev->mouse.button <= 5) + if (ev->mouse.display == bd->Display && ev->mouse.button <= 5) io.MouseDown[ev->mouse.button - 1] = (ev->type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN); return true; case ALLEGRO_EVENT_TOUCH_MOVE: - if (ev->touch.display == g_Display) + if (ev->touch.display == bd->Display) io.MousePos = ImVec2(ev->touch.x, ev->touch.y); return true; case ALLEGRO_EVENT_TOUCH_BEGIN: case ALLEGRO_EVENT_TOUCH_END: case ALLEGRO_EVENT_TOUCH_CANCEL: - if (ev->touch.display == g_Display && ev->touch.primary) + if (ev->touch.display == bd->Display && ev->touch.primary) io.MouseDown[0] = (ev->type == ALLEGRO_EVENT_TOUCH_BEGIN); return true; case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: - if (ev->mouse.display == g_Display) + if (ev->mouse.display == bd->Display) io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); return true; case ALLEGRO_EVENT_KEY_CHAR: - if (ev->keyboard.display == g_Display) + if (ev->keyboard.display == bd->Display) if (ev->keyboard.unichar != 0) io.AddInputCharacter((unsigned int)ev->keyboard.unichar); return true; case ALLEGRO_EVENT_KEY_DOWN: case ALLEGRO_EVENT_KEY_UP: - if (ev->keyboard.display == g_Display) + if (ev->keyboard.display == bd->Display) io.KeysDown[ev->keyboard.keycode] = (ev->type == ALLEGRO_EVENT_KEY_DOWN); return true; } @@ -376,11 +400,12 @@ static void ImGui_ImplAllegro5_UpdateMouseCursor() if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor - al_set_mouse_cursor(g_Display, g_MouseCursorInvisible); + al_set_mouse_cursor(bd->Display, bd->MouseCursorInvisible); } else { @@ -395,27 +420,30 @@ static void ImGui_ImplAllegro5_UpdateMouseCursor() case ImGuiMouseCursor_ResizeNWSE: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NW; break; case ImGuiMouseCursor_NotAllowed: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_UNAVAILABLE; break; } - al_set_system_mouse_cursor(g_Display, cursor_id); + al_set_system_mouse_cursor(bd->Display, cursor_id); } } void ImGui_ImplAllegro5_NewFrame() { - if (!g_Texture) + ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplAllegro5_Init()?"); + + if (!bd->Texture) ImGui_ImplAllegro5_CreateDeviceObjects(); ImGuiIO& io = ImGui::GetIO(); // Setup display size (every frame to accommodate for window resizing) int w, h; - w = al_get_display_width(g_Display); - h = al_get_display_height(g_Display); + w = al_get_display_width(bd->Display); + h = al_get_display_height(bd->Display); io.DisplaySize = ImVec2((float)w, (float)h); // Setup time step double current_time = al_get_time(); - io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f); - g_Time = current_time; + io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); + bd->Time = current_time; // Setup inputs ALLEGRO_KEYBOARD_STATE keys; diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.h b/examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.h similarity index 85% rename from examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.h index ef91d4b3..06c7120b 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_allegro5.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_allegro5.h @@ -9,7 +9,8 @@ // [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually. // [ ] Platform: Missing gamepad support. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.83/backends/imgui_impl_android.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_android.cpp new file mode 100644 index 00000000..aae8e6b8 --- /dev/null +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_android.cpp @@ -0,0 +1,187 @@ +// dear imgui: Platform Binding for Android native app +// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) + +// Implemented features: +// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE). +// Missing features: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. +// Important: +// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446) +// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446) + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2021-03-04: Initial version. + +#include "imgui.h" +#include "imgui_impl_android.h" +#include +#include +#include +#include +#include +#include +#include + +// Android data +static double g_Time = 0.0; +static ANativeWindow* g_Window; +static char g_LogTag[] = "ImGuiExample"; +static std::map> g_KeyEventQueues; // FIXME: Remove dependency on map and queue once we use upcoming input queue. + +int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event) +{ + ImGuiIO& io = ImGui::GetIO(); + int32_t event_type = AInputEvent_getType(input_event); + switch (event_type) + { + case AINPUT_EVENT_TYPE_KEY: + { + int32_t event_key_code = AKeyEvent_getKeyCode(input_event); + int32_t event_action = AKeyEvent_getAction(input_event); + int32_t event_meta_state = AKeyEvent_getMetaState(input_event); + + io.KeyCtrl = ((event_meta_state & AMETA_CTRL_ON) != 0); + io.KeyShift = ((event_meta_state & AMETA_SHIFT_ON) != 0); + io.KeyAlt = ((event_meta_state & AMETA_ALT_ON) != 0); + + switch (event_action) + { + // FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer + // goes up from a key. We use a simple key event queue/ and process one event per key per frame in + // ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787 + case AKEY_EVENT_ACTION_DOWN: + case AKEY_EVENT_ACTION_UP: + g_KeyEventQueues[event_key_code].push(event_action); + break; + default: + break; + } + break; + } + case AINPUT_EVENT_TYPE_MOTION: + { + int32_t event_action = AMotionEvent_getAction(input_event); + int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + event_action &= AMOTION_EVENT_ACTION_MASK; + switch (event_action) + { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_UP: + // Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP, + // but we have to process them separately to identify the actual button pressed. This is done below via + // AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback). + if((AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER) + || (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN)) + { + io.MouseDown[0] = (event_action == AMOTION_EVENT_ACTION_DOWN); + io.MousePos = ImVec2(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); + } + break; + case AMOTION_EVENT_ACTION_BUTTON_PRESS: + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: + { + int32_t button_state = AMotionEvent_getButtonState(input_event); + io.MouseDown[0] = ((button_state & AMOTION_EVENT_BUTTON_PRIMARY) != 0); + io.MouseDown[1] = ((button_state & AMOTION_EVENT_BUTTON_SECONDARY) != 0); + io.MouseDown[2] = ((button_state & AMOTION_EVENT_BUTTON_TERTIARY) != 0); + } + break; + case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse) + case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN + io.MousePos = ImVec2(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); + break; + case AMOTION_EVENT_ACTION_SCROLL: + io.MouseWheel = AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index); + io.MouseWheelH = AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index); + break; + default: + break; + } + } + return 1; + default: + break; + } + + return 0; +} + +bool ImGui_ImplAndroid_Init(ANativeWindow* window) +{ + g_Window = window; + g_Time = 0.0; + + // Setup backend capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendPlatformName = "imgui_impl_android"; + + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = AKEYCODE_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = AKEYCODE_DPAD_LEFT; // also covers physical keyboard arrow key + io.KeyMap[ImGuiKey_RightArrow] = AKEYCODE_DPAD_RIGHT; // also covers physical keyboard arrow key + io.KeyMap[ImGuiKey_UpArrow] = AKEYCODE_DPAD_UP; // also covers physical keyboard arrow key + io.KeyMap[ImGuiKey_DownArrow] = AKEYCODE_DPAD_DOWN; // also covers physical keyboard arrow key + io.KeyMap[ImGuiKey_PageUp] = AKEYCODE_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = AKEYCODE_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = AKEYCODE_MOVE_HOME; + io.KeyMap[ImGuiKey_End] = AKEYCODE_MOVE_END; + io.KeyMap[ImGuiKey_Insert] = AKEYCODE_INSERT; + io.KeyMap[ImGuiKey_Delete] = AKEYCODE_FORWARD_DEL; + io.KeyMap[ImGuiKey_Backspace] = AKEYCODE_DEL; + io.KeyMap[ImGuiKey_Space] = AKEYCODE_SPACE; + io.KeyMap[ImGuiKey_Enter] = AKEYCODE_ENTER; + io.KeyMap[ImGuiKey_Escape] = AKEYCODE_ESCAPE; + io.KeyMap[ImGuiKey_KeyPadEnter] = AKEYCODE_NUMPAD_ENTER; + io.KeyMap[ImGuiKey_A] = AKEYCODE_A; + io.KeyMap[ImGuiKey_C] = AKEYCODE_C; + io.KeyMap[ImGuiKey_V] = AKEYCODE_V; + io.KeyMap[ImGuiKey_X] = AKEYCODE_X; + io.KeyMap[ImGuiKey_Y] = AKEYCODE_Y; + io.KeyMap[ImGuiKey_Z] = AKEYCODE_Z; + + return true; +} + +void ImGui_ImplAndroid_Shutdown() +{ +} + +void ImGui_ImplAndroid_NewFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Process queued key events + // FIXME: This is a workaround for multiple key event actions occurring at once (see above) and can be removed once we use upcoming input queue. + for (auto& key_queue : g_KeyEventQueues) + { + if (key_queue.second.empty()) + continue; + io.KeysDown[key_queue.first] = (key_queue.second.front() == AKEY_EVENT_ACTION_DOWN); + key_queue.second.pop(); + } + + // Setup display size (every frame to accommodate for window resizing) + int32_t window_width = ANativeWindow_getWidth(g_Window); + int32_t window_height = ANativeWindow_getHeight(g_Window); + int display_width = window_width; + int display_height = window_height; + + io.DisplaySize = ImVec2((float)window_width, (float)window_height); + if (window_width > 0 && window_height > 0) + io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height); + + // Setup time step + struct timespec current_timespec; + clock_gettime(CLOCK_MONOTONIC, ¤t_timespec); + double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f); + g_Time = current_time; +} diff --git a/examples/interactive/imgui-1.83/backends/imgui_impl_android.h b/examples/interactive/imgui-1.83/backends/imgui_impl_android.h new file mode 100644 index 00000000..92b466b6 --- /dev/null +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_android.h @@ -0,0 +1,27 @@ +// dear imgui: Platform Binding for Android native app +// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) + +// Implemented features: +// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE). +// Missing features: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. +// Important: +// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446) +// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446) + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +#pragma once + +struct ANativeWindow; +struct AInputEvent; + +IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window); +IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event); +IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame(); diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx10.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_dx10.cpp similarity index 71% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx10.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx10.cpp index a504dcbe..671e4005 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx10.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx10.cpp @@ -6,13 +6,17 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: DirectX10: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: DirectX10: Change blending equation to preserve alpha in output buffer. // 2019-07-21: DirectX10: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData(). // 2019-05-29: DirectX10: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: DirectX10: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. @@ -39,32 +43,48 @@ #endif // DirectX data -static ID3D10Device* g_pd3dDevice = NULL; -static IDXGIFactory* g_pFactory = NULL; -static ID3D10Buffer* g_pVB = NULL; -static ID3D10Buffer* g_pIB = NULL; -static ID3D10VertexShader* g_pVertexShader = NULL; -static ID3D10InputLayout* g_pInputLayout = NULL; -static ID3D10Buffer* g_pVertexConstantBuffer = NULL; -static ID3D10PixelShader* g_pPixelShader = NULL; -static ID3D10SamplerState* g_pFontSampler = NULL; -static ID3D10ShaderResourceView*g_pFontTextureView = NULL; -static ID3D10RasterizerState* g_pRasterizerState = NULL; -static ID3D10BlendState* g_pBlendState = NULL; -static ID3D10DepthStencilState* g_pDepthStencilState = NULL; -static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000; +struct ImGui_ImplDX10_Data +{ + ID3D10Device* pd3dDevice; + IDXGIFactory* pFactory; + ID3D10Buffer* pVB; + ID3D10Buffer* pIB; + ID3D10VertexShader* pVertexShader; + ID3D10InputLayout* pInputLayout; + ID3D10Buffer* pVertexConstantBuffer; + ID3D10PixelShader* pPixelShader; + ID3D10SamplerState* pFontSampler; + ID3D10ShaderResourceView* pFontTextureView; + ID3D10RasterizerState* pRasterizerState; + ID3D10BlendState* pBlendState; + ID3D10DepthStencilState* pDepthStencilState; + int VertexBufferSize; + int IndexBufferSize; + + ImGui_ImplDX10_Data() { memset(this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } +}; struct VERTEX_CONSTANT_BUFFER { float mvp[4][4]; }; +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplDX10_Data* ImGui_ImplDX10_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplDX10_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + // Forward Declarations static void ImGui_ImplDX10_InitPlatformInterface(); static void ImGui_ImplDX10_ShutdownPlatformInterface(); +// Functions static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* ctx) { + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + // Setup viewport D3D10_VIEWPORT vp; memset(&vp, 0, sizeof(D3D10_VIEWPORT)); @@ -78,21 +98,21 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* // Bind shader and vertex buffers unsigned int stride = sizeof(ImDrawVert); unsigned int offset = 0; - ctx->IASetInputLayout(g_pInputLayout); - ctx->IASetVertexBuffers(0, 1, &g_pVB, &stride, &offset); - ctx->IASetIndexBuffer(g_pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); + ctx->IASetInputLayout(bd->pInputLayout); + ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset); + ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); ctx->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ctx->VSSetShader(g_pVertexShader); - ctx->VSSetConstantBuffers(0, 1, &g_pVertexConstantBuffer); - ctx->PSSetShader(g_pPixelShader); - ctx->PSSetSamplers(0, 1, &g_pFontSampler); + ctx->VSSetShader(bd->pVertexShader); + ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer); + ctx->PSSetShader(bd->pPixelShader); + ctx->PSSetSamplers(0, 1, &bd->pFontSampler); ctx->GSSetShader(NULL); // Setup render state const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - ctx->OMSetBlendState(g_pBlendState, blend_factor, 0xffffffff); - ctx->OMSetDepthStencilState(g_pDepthStencilState, 0); - ctx->RSSetState(g_pRasterizerState); + ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); + ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0); + ctx->RSSetState(bd->pRasterizerState); } // Render function @@ -102,43 +122,44 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; - ID3D10Device* ctx = g_pd3dDevice; + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + ID3D10Device* ctx = bd->pd3dDevice; // Create and grow vertex/index buffers if needed - if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) + if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - g_VertexBufferSize = draw_data->TotalVtxCount + 5000; + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; D3D10_BUFFER_DESC desc; memset(&desc, 0, sizeof(D3D10_BUFFER_DESC)); desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = g_VertexBufferSize * sizeof(ImDrawVert); + desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert); desc.BindFlags = D3D10_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; desc.MiscFlags = 0; - if (ctx->CreateBuffer(&desc, NULL, &g_pVB) < 0) + if (ctx->CreateBuffer(&desc, NULL, &bd->pVB) < 0) return; } - if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) + if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) { - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - g_IndexBufferSize = draw_data->TotalIdxCount + 10000; + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; D3D10_BUFFER_DESC desc; memset(&desc, 0, sizeof(D3D10_BUFFER_DESC)); desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = g_IndexBufferSize * sizeof(ImDrawIdx); + desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx); desc.BindFlags = D3D10_BIND_INDEX_BUFFER; desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; - if (ctx->CreateBuffer(&desc, NULL, &g_pIB) < 0) + if (ctx->CreateBuffer(&desc, NULL, &bd->pIB) < 0) return; } // Copy and convert all vertices into a single contiguous buffer ImDrawVert* vtx_dst = NULL; ImDrawIdx* idx_dst = NULL; - g_pVB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&vtx_dst); - g_pIB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&idx_dst); + bd->pVB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&vtx_dst); + bd->pIB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&idx_dst); for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; @@ -147,14 +168,14 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) vtx_dst += cmd_list->VtxBuffer.Size; idx_dst += cmd_list->IdxBuffer.Size; } - g_pVB->Unmap(); - g_pIB->Unmap(); + bd->pVB->Unmap(); + bd->pIB->Unmap(); // Setup orthographic projection matrix into our constant buffer // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. { void* mapped_resource; - if (g_pVertexConstantBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) + if (bd->pVertexConstantBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) return; VERTEX_CONSTANT_BUFFER* constant_buffer = (VERTEX_CONSTANT_BUFFER*)mapped_resource; float L = draw_data->DisplayPos.x; @@ -169,7 +190,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, }; memcpy(&constant_buffer->mvp, mvp, sizeof(mvp)); - g_pVertexConstantBuffer->Unmap(); + bd->pVertexConstantBuffer->Unmap(); } // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!) @@ -195,7 +216,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) DXGI_FORMAT IndexBufferFormat; ID3D10InputLayout* InputLayout; }; - BACKUP_DX10_STATE old; + BACKUP_DX10_STATE old = {}; old.ScissorRectsCount = old.ViewportsCount = D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects); ctx->RSGetViewports(&old.ViewportsCount, old.Viewports); @@ -243,7 +264,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) ctx->RSSetScissorRects(1, &r); // Bind texture, Draw - ID3D10ShaderResourceView* texture_srv = (ID3D10ShaderResourceView*)pcmd->TextureId; + ID3D10ShaderResourceView* texture_srv = (ID3D10ShaderResourceView*)pcmd->GetTexID(); ctx->PSSetShaderResources(0, 1, &texture_srv); ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset); } @@ -273,6 +294,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) static void ImGui_ImplDX10_CreateFontsTexture() { // Build texture atlas + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; @@ -297,7 +319,8 @@ static void ImGui_ImplDX10_CreateFontsTexture() subResource.pSysMem = pixels; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + IM_ASSERT(pTexture != NULL); // Create texture view D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc; @@ -306,12 +329,12 @@ static void ImGui_ImplDX10_CreateFontsTexture() srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; srv_desc.Texture2D.MipLevels = desc.MipLevels; srv_desc.Texture2D.MostDetailedMip = 0; - g_pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &g_pFontTextureView); + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &bd->pFontTextureView); pTexture->Release(); } // Store our identifier - io.Fonts->SetTexID((ImTextureID)g_pFontTextureView); + io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); // Create texture sampler { @@ -325,15 +348,16 @@ static void ImGui_ImplDX10_CreateFontsTexture() desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS; desc.MinLOD = 0.f; desc.MaxLOD = 0.f; - g_pd3dDevice->CreateSamplerState(&desc, &g_pFontSampler); + bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); } } bool ImGui_ImplDX10_CreateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + if (!bd->pd3dDevice) return false; - if (g_pFontSampler) + if (bd->pFontSampler) ImGui_ImplDX10_InvalidateDeviceObjects(); // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) @@ -375,7 +399,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() ID3DBlob* vertexShaderBlob; if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vertexShaderBlob, NULL))) return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (g_pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &g_pVertexShader) != S_OK) + if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pVertexShader) != S_OK) { vertexShaderBlob->Release(); return false; @@ -388,7 +412,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D10_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; - if (g_pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &g_pInputLayout) != S_OK) + if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK) { vertexShaderBlob->Release(); return false; @@ -403,7 +427,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.BindFlags = D3D10_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; desc.MiscFlags = 0; - g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pVertexConstantBuffer); + bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pVertexConstantBuffer); } } @@ -428,7 +452,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() ID3DBlob* pixelShaderBlob; if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &pixelShaderBlob, NULL))) return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (g_pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), &g_pPixelShader) != S_OK) + if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), &bd->pPixelShader) != S_OK) { pixelShaderBlob->Release(); return false; @@ -445,11 +469,11 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.SrcBlend = D3D10_BLEND_SRC_ALPHA; desc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA; desc.BlendOp = D3D10_BLEND_OP_ADD; - desc.SrcBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; - desc.DestBlendAlpha = D3D10_BLEND_ZERO; + desc.SrcBlendAlpha = D3D10_BLEND_ONE; + desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; desc.BlendOpAlpha = D3D10_BLEND_OP_ADD; desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL; - g_pd3dDevice->CreateBlendState(&desc, &g_pBlendState); + bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState); } // Create the rasterizer state @@ -460,7 +484,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.CullMode = D3D10_CULL_NONE; desc.ScissorEnable = true; desc.DepthClipEnable = true; - g_pd3dDevice->CreateRasterizerState(&desc, &g_pRasterizerState); + bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState); } // Create depth-stencil State @@ -474,7 +498,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP; desc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS; desc.BackFace = desc.FrontFace; - g_pd3dDevice->CreateDepthStencilState(&desc, &g_pDepthStencilState); + bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState); } ImGui_ImplDX10_CreateFontsTexture(); @@ -484,27 +508,31 @@ bool ImGui_ImplDX10_CreateDeviceObjects() void ImGui_ImplDX10_InvalidateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + if (!bd->pd3dDevice) return; - if (g_pFontSampler) { g_pFontSampler->Release(); g_pFontSampler = NULL; } - if (g_pFontTextureView) { g_pFontTextureView->Release(); g_pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - - if (g_pBlendState) { g_pBlendState->Release(); g_pBlendState = NULL; } - if (g_pDepthStencilState) { g_pDepthStencilState->Release(); g_pDepthStencilState = NULL; } - if (g_pRasterizerState) { g_pRasterizerState->Release(); g_pRasterizerState = NULL; } - if (g_pPixelShader) { g_pPixelShader->Release(); g_pPixelShader = NULL; } - if (g_pVertexConstantBuffer) { g_pVertexConstantBuffer->Release(); g_pVertexConstantBuffer = NULL; } - if (g_pInputLayout) { g_pInputLayout->Release(); g_pInputLayout = NULL; } - if (g_pVertexShader) { g_pVertexShader->Release(); g_pVertexShader = NULL; } + if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = NULL; } + if (bd->pFontTextureView) { bd->pFontTextureView->Release(); bd->pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = NULL; } + if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = NULL; } + if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = NULL; } + if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = NULL; } + if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = NULL; } + if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = NULL; } + if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = NULL; } } bool ImGui_ImplDX10_Init(ID3D10Device* device) { - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX10_Data* bd = IM_NEW(ImGui_ImplDX10_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx10"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) @@ -513,17 +541,16 @@ bool ImGui_ImplDX10_Init(ID3D10Device* device) IDXGIDevice* pDXGIDevice = NULL; IDXGIAdapter* pDXGIAdapter = NULL; IDXGIFactory* pFactory = NULL; - if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK) if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK) if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK) { - g_pd3dDevice = device; - g_pFactory = pFactory; + bd->pd3dDevice = device; + bd->pFactory = pFactory; } if (pDXGIDevice) pDXGIDevice->Release(); if (pDXGIAdapter) pDXGIAdapter->Release(); - g_pd3dDevice->AddRef(); + bd->pd3dDevice->AddRef(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplDX10_InitPlatformInterface(); @@ -532,15 +559,24 @@ bool ImGui_ImplDX10_Init(ID3D10Device* device) void ImGui_ImplDX10_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + ImGui_ImplDX10_ShutdownPlatformInterface(); ImGui_ImplDX10_InvalidateDeviceObjects(); - if (g_pFactory) { g_pFactory->Release(); g_pFactory = NULL; } - if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; } + if (bd->pFactory) { bd->pFactory->Release(); } + if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplDX10_NewFrame() { - if (!g_pFontSampler) + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplDX10_Init()?"); + + if (!bd->pFontSampler) ImGui_ImplDX10_CreateDeviceObjects(); } @@ -551,19 +587,20 @@ void ImGui_ImplDX10_NewFrame() //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataDx10 +struct ImGui_ImplDX10_ViewportData { - IDXGISwapChain* SwapChain; - ID3D10RenderTargetView* RTView; + IDXGISwapChain* SwapChain; + ID3D10RenderTargetView* RTView; - ImGuiViewportDataDx10() { SwapChain = NULL; RTView = NULL; } - ~ImGuiViewportDataDx10() { IM_ASSERT(SwapChain == NULL && RTView == NULL); } + ImGui_ImplDX10_ViewportData() { SwapChain = NULL; RTView = NULL; } + ~ImGui_ImplDX10_ViewportData() { IM_ASSERT(SwapChain == NULL && RTView == NULL); } }; static void ImGui_ImplDX10_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataDx10* data = IM_NEW(ImGuiViewportDataDx10)(); - viewport->RendererUserData = data; + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + ImGui_ImplDX10_ViewportData* vd = IM_NEW(ImGui_ImplDX10_ViewportData)(); + viewport->RendererUserData = vd; // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). // Some backends will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. @@ -585,15 +622,15 @@ static void ImGui_ImplDX10_CreateWindow(ImGuiViewport* viewport) sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = 0; - IM_ASSERT(data->SwapChain == NULL && data->RTView == NULL); - g_pFactory->CreateSwapChain(g_pd3dDevice, &sd, &data->SwapChain); + IM_ASSERT(vd->SwapChain == NULL && vd->RTView == NULL); + bd->pFactory->CreateSwapChain(bd->pd3dDevice, &sd, &vd->SwapChain); // Create the render target - if (data->SwapChain) + if (vd->SwapChain) { ID3D10Texture2D* pBackBuffer; - data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); - g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &vd->RTView); pBackBuffer->Release(); } } @@ -601,52 +638,54 @@ static void ImGui_ImplDX10_CreateWindow(ImGuiViewport* viewport) static void ImGui_ImplDX10_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == NULL here since we didn't create the data for it. - if (ImGuiViewportDataDx10* data = (ImGuiViewportDataDx10*)viewport->RendererUserData) + if (ImGui_ImplDX10_ViewportData* vd = (ImGui_ImplDX10_ViewportData*)viewport->RendererUserData) { - if (data->SwapChain) - data->SwapChain->Release(); - data->SwapChain = NULL; - if (data->RTView) - data->RTView->Release(); - data->RTView = NULL; - IM_DELETE(data); + if (vd->SwapChain) + vd->SwapChain->Release(); + vd->SwapChain = NULL; + if (vd->RTView) + vd->RTView->Release(); + vd->RTView = NULL; + IM_DELETE(vd); } viewport->RendererUserData = NULL; } static void ImGui_ImplDX10_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataDx10* data = (ImGuiViewportDataDx10*)viewport->RendererUserData; - if (data->RTView) + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + ImGui_ImplDX10_ViewportData* vd = (ImGui_ImplDX10_ViewportData*)viewport->RendererUserData; + if (vd->RTView) { - data->RTView->Release(); - data->RTView = NULL; + vd->RTView->Release(); + vd->RTView = NULL; } - if (data->SwapChain) + if (vd->SwapChain) { ID3D10Texture2D* pBackBuffer = NULL; - data->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); - data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); + vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); if (pBackBuffer == NULL) { fprintf(stderr, "ImGui_ImplDX10_SetWindowSize() failed creating buffers.\n"); return; } - g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &vd->RTView); pBackBuffer->Release(); } } static void ImGui_ImplDX10_RenderViewport(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx10* data = (ImGuiViewportDataDx10*)viewport->RendererUserData; + ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); + ImGui_ImplDX10_ViewportData* vd = (ImGui_ImplDX10_ViewportData*)viewport->RendererUserData; ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); - g_pd3dDevice->OMSetRenderTargets(1, &data->RTView, NULL); + bd->pd3dDevice->OMSetRenderTargets(1, &vd->RTView, NULL); if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) - g_pd3dDevice->ClearRenderTargetView(data->RTView, (float*)&clear_color); + bd->pd3dDevice->ClearRenderTargetView(vd->RTView, (float*)&clear_color); ImGui_ImplDX10_RenderDrawData(viewport->DrawData); } static void ImGui_ImplDX10_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx10* data = (ImGuiViewportDataDx10*)viewport->RendererUserData; - data->SwapChain->Present(0, 0); // Present without vsync + ImGui_ImplDX10_ViewportData* vd = (ImGui_ImplDX10_ViewportData*)viewport->RendererUserData; + vd->SwapChain->Present(0, 0); // Present without vsync } void ImGui_ImplDX10_InitPlatformInterface() diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx10.h b/examples/interactive/imgui-1.83/backends/imgui_impl_dx10.h similarity index 81% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx10.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx10.h index 2905ef5d..84ec353f 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx10.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx10.h @@ -6,7 +6,8 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx11.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_dx11.cpp similarity index 72% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx11.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx11.cpp index 42d764a5..e14b0ce3 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx11.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx11.cpp @@ -6,13 +6,17 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer. // 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled). // 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore. // 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. @@ -38,34 +42,50 @@ #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. #endif -// DirectX data -static ID3D11Device* g_pd3dDevice = NULL; -static ID3D11DeviceContext* g_pd3dDeviceContext = NULL; -static IDXGIFactory* g_pFactory = NULL; -static ID3D11Buffer* g_pVB = NULL; -static ID3D11Buffer* g_pIB = NULL; -static ID3D11VertexShader* g_pVertexShader = NULL; -static ID3D11InputLayout* g_pInputLayout = NULL; -static ID3D11Buffer* g_pVertexConstantBuffer = NULL; -static ID3D11PixelShader* g_pPixelShader = NULL; -static ID3D11SamplerState* g_pFontSampler = NULL; -static ID3D11ShaderResourceView*g_pFontTextureView = NULL; -static ID3D11RasterizerState* g_pRasterizerState = NULL; -static ID3D11BlendState* g_pBlendState = NULL; -static ID3D11DepthStencilState* g_pDepthStencilState = NULL; -static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000; +// DirectX11 data +struct ImGui_ImplDX11_Data +{ + ID3D11Device* pd3dDevice; + ID3D11DeviceContext* pd3dDeviceContext; + IDXGIFactory* pFactory; + ID3D11Buffer* pVB; + ID3D11Buffer* pIB; + ID3D11VertexShader* pVertexShader; + ID3D11InputLayout* pInputLayout; + ID3D11Buffer* pVertexConstantBuffer; + ID3D11PixelShader* pPixelShader; + ID3D11SamplerState* pFontSampler; + ID3D11ShaderResourceView* pFontTextureView; + ID3D11RasterizerState* pRasterizerState; + ID3D11BlendState* pBlendState; + ID3D11DepthStencilState* pDepthStencilState; + int VertexBufferSize; + int IndexBufferSize; + + ImGui_ImplDX11_Data() { memset(this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } +}; struct VERTEX_CONSTANT_BUFFER { float mvp[4][4]; }; +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + // Forward Declarations static void ImGui_ImplDX11_InitPlatformInterface(); static void ImGui_ImplDX11_ShutdownPlatformInterface(); +// Functions static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx) { + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + // Setup viewport D3D11_VIEWPORT vp; memset(&vp, 0, sizeof(D3D11_VIEWPORT)); @@ -79,14 +99,14 @@ static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceC // Setup shader and vertex buffers unsigned int stride = sizeof(ImDrawVert); unsigned int offset = 0; - ctx->IASetInputLayout(g_pInputLayout); - ctx->IASetVertexBuffers(0, 1, &g_pVB, &stride, &offset); - ctx->IASetIndexBuffer(g_pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); + ctx->IASetInputLayout(bd->pInputLayout); + ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset); + ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ctx->VSSetShader(g_pVertexShader, NULL, 0); - ctx->VSSetConstantBuffers(0, 1, &g_pVertexConstantBuffer); - ctx->PSSetShader(g_pPixelShader, NULL, 0); - ctx->PSSetSamplers(0, 1, &g_pFontSampler); + ctx->VSSetShader(bd->pVertexShader, NULL, 0); + ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer); + ctx->PSSetShader(bd->pPixelShader, NULL, 0); + ctx->PSSetSamplers(0, 1, &bd->pFontSampler); ctx->GSSetShader(NULL, NULL, 0); ctx->HSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used.. ctx->DSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used.. @@ -94,9 +114,9 @@ static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceC // Setup blend state const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - ctx->OMSetBlendState(g_pBlendState, blend_factor, 0xffffffff); - ctx->OMSetDepthStencilState(g_pDepthStencilState, 0); - ctx->RSSetState(g_pRasterizerState); + ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); + ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0); + ctx->RSSetState(bd->pRasterizerState); } // Render function @@ -106,42 +126,43 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; - ID3D11DeviceContext* ctx = g_pd3dDeviceContext; + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + ID3D11DeviceContext* ctx = bd->pd3dDeviceContext; // Create and grow vertex/index buffers if needed - if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) + if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - g_VertexBufferSize = draw_data->TotalVtxCount + 5000; + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; D3D11_BUFFER_DESC desc; memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = g_VertexBufferSize * sizeof(ImDrawVert); + desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert); desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; desc.MiscFlags = 0; - if (g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pVB) < 0) + if (bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pVB) < 0) return; } - if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) + if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) { - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - g_IndexBufferSize = draw_data->TotalIdxCount + 10000; + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; D3D11_BUFFER_DESC desc; memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = g_IndexBufferSize * sizeof(ImDrawIdx); + desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx); desc.BindFlags = D3D11_BIND_INDEX_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - if (g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pIB) < 0) + if (bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pIB) < 0) return; } // Upload vertex/index data into a single contiguous GPU buffer D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource; - if (ctx->Map(g_pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK) + if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK) return; - if (ctx->Map(g_pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK) + if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK) return; ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData; ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData; @@ -153,14 +174,14 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) vtx_dst += cmd_list->VtxBuffer.Size; idx_dst += cmd_list->IdxBuffer.Size; } - ctx->Unmap(g_pVB, 0); - ctx->Unmap(g_pIB, 0); + ctx->Unmap(bd->pVB, 0); + ctx->Unmap(bd->pIB, 0); // Setup orthographic projection matrix into our constant buffer // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. { D3D11_MAPPED_SUBRESOURCE mapped_resource; - if (ctx->Map(g_pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) + if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) return; VERTEX_CONSTANT_BUFFER* constant_buffer = (VERTEX_CONSTANT_BUFFER*)mapped_resource.pData; float L = draw_data->DisplayPos.x; @@ -175,7 +196,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, }; memcpy(&constant_buffer->mvp, mvp, sizeof(mvp)); - ctx->Unmap(g_pVertexConstantBuffer, 0); + ctx->Unmap(bd->pVertexConstantBuffer, 0); } // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!) @@ -203,7 +224,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) DXGI_FORMAT IndexBufferFormat; ID3D11InputLayout* InputLayout; }; - BACKUP_DX11_STATE old; + BACKUP_DX11_STATE old = {}; old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects); ctx->RSGetViewports(&old.ViewportsCount, old.Viewports); @@ -253,7 +274,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) ctx->RSSetScissorRects(1, &r); // Bind texture, Draw - ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->TextureId; + ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID(); ctx->PSSetShaderResources(0, 1, &texture_srv); ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset); } @@ -286,6 +307,7 @@ static void ImGui_ImplDX11_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); @@ -309,7 +331,8 @@ static void ImGui_ImplDX11_CreateFontsTexture() subResource.pSysMem = pixels; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + IM_ASSERT(pTexture != NULL); // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; @@ -318,12 +341,12 @@ static void ImGui_ImplDX11_CreateFontsTexture() srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; - g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &g_pFontTextureView); + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); pTexture->Release(); } // Store our identifier - io.Fonts->SetTexID((ImTextureID)g_pFontTextureView); + io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); // Create texture sampler { @@ -337,15 +360,16 @@ static void ImGui_ImplDX11_CreateFontsTexture() desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; desc.MinLOD = 0.f; desc.MaxLOD = 0.f; - g_pd3dDevice->CreateSamplerState(&desc, &g_pFontSampler); + bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); } } bool ImGui_ImplDX11_CreateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + if (!bd->pd3dDevice) return false; - if (g_pFontSampler) + if (bd->pFontSampler) ImGui_ImplDX11_InvalidateDeviceObjects(); // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) @@ -387,7 +411,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() ID3DBlob* vertexShaderBlob; if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vertexShaderBlob, NULL))) return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (g_pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), NULL, &g_pVertexShader) != S_OK) + if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), NULL, &bd->pVertexShader) != S_OK) { vertexShaderBlob->Release(); return false; @@ -400,7 +424,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; - if (g_pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &g_pInputLayout) != S_OK) + if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK) { vertexShaderBlob->Release(); return false; @@ -415,7 +439,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; desc.MiscFlags = 0; - g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pVertexConstantBuffer); + bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pVertexConstantBuffer); } } @@ -440,7 +464,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() ID3DBlob* pixelShaderBlob; if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &pixelShaderBlob, NULL))) return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (g_pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), NULL, &g_pPixelShader) != S_OK) + if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), NULL, &bd->pPixelShader) != S_OK) { pixelShaderBlob->Release(); return false; @@ -457,11 +481,11 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - g_pd3dDevice->CreateBlendState(&desc, &g_pBlendState); + bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState); } // Create the rasterizer state @@ -472,7 +496,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.CullMode = D3D11_CULL_NONE; desc.ScissorEnable = true; desc.DepthClipEnable = true; - g_pd3dDevice->CreateRasterizerState(&desc, &g_pRasterizerState); + bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState); } // Create depth-stencil State @@ -486,7 +510,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; desc.BackFace = desc.FrontFace; - g_pd3dDevice->CreateDepthStencilState(&desc, &g_pDepthStencilState); + bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState); } ImGui_ImplDX11_CreateFontsTexture(); @@ -496,27 +520,31 @@ bool ImGui_ImplDX11_CreateDeviceObjects() void ImGui_ImplDX11_InvalidateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + if (!bd->pd3dDevice) return; - if (g_pFontSampler) { g_pFontSampler->Release(); g_pFontSampler = NULL; } - if (g_pFontTextureView) { g_pFontTextureView->Release(); g_pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - - if (g_pBlendState) { g_pBlendState->Release(); g_pBlendState = NULL; } - if (g_pDepthStencilState) { g_pDepthStencilState->Release(); g_pDepthStencilState = NULL; } - if (g_pRasterizerState) { g_pRasterizerState->Release(); g_pRasterizerState = NULL; } - if (g_pPixelShader) { g_pPixelShader->Release(); g_pPixelShader = NULL; } - if (g_pVertexConstantBuffer) { g_pVertexConstantBuffer->Release(); g_pVertexConstantBuffer = NULL; } - if (g_pInputLayout) { g_pInputLayout->Release(); g_pInputLayout = NULL; } - if (g_pVertexShader) { g_pVertexShader->Release(); g_pVertexShader = NULL; } + if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = NULL; } + if (bd->pFontTextureView) { bd->pFontTextureView->Release(); bd->pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = NULL; } + if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = NULL; } + if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = NULL; } + if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = NULL; } + if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = NULL; } + if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = NULL; } + if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = NULL; } } bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context) { - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx11"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) @@ -530,14 +558,14 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK) if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK) { - g_pd3dDevice = device; - g_pd3dDeviceContext = device_context; - g_pFactory = pFactory; + bd->pd3dDevice = device; + bd->pd3dDeviceContext = device_context; + bd->pFactory = pFactory; } if (pDXGIDevice) pDXGIDevice->Release(); if (pDXGIAdapter) pDXGIAdapter->Release(); - g_pd3dDevice->AddRef(); - g_pd3dDeviceContext->AddRef(); + bd->pd3dDevice->AddRef(); + bd->pd3dDeviceContext->AddRef(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplDX11_InitPlatformInterface(); @@ -547,16 +575,25 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co void ImGui_ImplDX11_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + ImGui_ImplDX11_ShutdownPlatformInterface(); ImGui_ImplDX11_InvalidateDeviceObjects(); - if (g_pFactory) { g_pFactory->Release(); g_pFactory = NULL; } - if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; } - if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = NULL; } + if (bd->pFactory) { bd->pFactory->Release(); } + if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } + if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); } + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplDX11_NewFrame() { - if (!g_pFontSampler) + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplDX11_Init()?"); + + if (!bd->pFontSampler) ImGui_ImplDX11_CreateDeviceObjects(); } @@ -567,19 +604,20 @@ void ImGui_ImplDX11_NewFrame() //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataDx11 +struct ImGui_ImplDX11_ViewportData { - IDXGISwapChain* SwapChain; - ID3D11RenderTargetView* RTView; + IDXGISwapChain* SwapChain; + ID3D11RenderTargetView* RTView; - ImGuiViewportDataDx11() { SwapChain = NULL; RTView = NULL; } - ~ImGuiViewportDataDx11() { IM_ASSERT(SwapChain == NULL && RTView == NULL); } + ImGui_ImplDX11_ViewportData() { SwapChain = NULL; RTView = NULL; } + ~ImGui_ImplDX11_ViewportData() { IM_ASSERT(SwapChain == NULL && RTView == NULL); } }; static void ImGui_ImplDX11_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataDx11* data = IM_NEW(ImGuiViewportDataDx11)(); - viewport->RendererUserData = data; + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + ImGui_ImplDX11_ViewportData* vd = IM_NEW(ImGui_ImplDX11_ViewportData)(); + viewport->RendererUserData = vd; // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). // Some backend will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. @@ -601,15 +639,15 @@ static void ImGui_ImplDX11_CreateWindow(ImGuiViewport* viewport) sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = 0; - IM_ASSERT(data->SwapChain == NULL && data->RTView == NULL); - g_pFactory->CreateSwapChain(g_pd3dDevice, &sd, &data->SwapChain); + IM_ASSERT(vd->SwapChain == NULL && vd->RTView == NULL); + bd->pFactory->CreateSwapChain(bd->pd3dDevice, &sd, &vd->SwapChain); // Create the render target - if (data->SwapChain) + if (vd->SwapChain) { ID3D11Texture2D* pBackBuffer; - data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); - g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &vd->RTView); pBackBuffer->Release(); } } @@ -617,52 +655,54 @@ static void ImGui_ImplDX11_CreateWindow(ImGuiViewport* viewport) static void ImGui_ImplDX11_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. - if (ImGuiViewportDataDx11* data = (ImGuiViewportDataDx11*)viewport->RendererUserData) + if (ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData) { - if (data->SwapChain) - data->SwapChain->Release(); - data->SwapChain = NULL; - if (data->RTView) - data->RTView->Release(); - data->RTView = NULL; - IM_DELETE(data); + if (vd->SwapChain) + vd->SwapChain->Release(); + vd->SwapChain = NULL; + if (vd->RTView) + vd->RTView->Release(); + vd->RTView = NULL; + IM_DELETE(vd); } viewport->RendererUserData = NULL; } static void ImGui_ImplDX11_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataDx11* data = (ImGuiViewportDataDx11*)viewport->RendererUserData; - if (data->RTView) + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData; + if (vd->RTView) { - data->RTView->Release(); - data->RTView = NULL; + vd->RTView->Release(); + vd->RTView = NULL; } - if (data->SwapChain) + if (vd->SwapChain) { ID3D11Texture2D* pBackBuffer = NULL; - data->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); - data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); + vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); if (pBackBuffer == NULL) { fprintf(stderr, "ImGui_ImplDX11_SetWindowSize() failed creating buffers.\n"); return; } - g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &vd->RTView); pBackBuffer->Release(); } } static void ImGui_ImplDX11_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx11* data = (ImGuiViewportDataDx11*)viewport->RendererUserData; + ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); + ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData; ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); - g_pd3dDeviceContext->OMSetRenderTargets(1, &data->RTView, NULL); + bd->pd3dDeviceContext->OMSetRenderTargets(1, &vd->RTView, NULL); if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) - g_pd3dDeviceContext->ClearRenderTargetView(data->RTView, (float*)&clear_color); + bd->pd3dDeviceContext->ClearRenderTargetView(vd->RTView, (float*)&clear_color); ImGui_ImplDX11_RenderDrawData(viewport->DrawData); } static void ImGui_ImplDX11_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx11* data = (ImGuiViewportDataDx11*)viewport->RendererUserData; - data->SwapChain->Present(0, 0); // Present without vsync + ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData; + vd->SwapChain->Present(0, 0); // Present without vsync } static void ImGui_ImplDX11_InitPlatformInterface() diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx11.h b/examples/interactive/imgui-1.83/backends/imgui_impl_dx11.h similarity index 82% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx11.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx11.h index cd8e9e11..07495a01 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx11.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx11.h @@ -6,7 +6,8 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx12.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_dx12.cpp similarity index 72% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx12.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx12.cpp index f6d6c472..e16f82b2 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx12.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx12.cpp @@ -9,15 +9,23 @@ // Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. // This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. -// This define is set in the example .vcxproj file and need to be replicated in your app or by adding it to your imconfig.h file. - -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// To build this on 32-bit systems: +// - [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file) +// - [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. +// - [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) +// - [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file) + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: DirectX12: Change blending equation to preserve alpha in output buffer. // 2021-01-11: DirectX12: Improve Windows 7 compatibility (for D3D12On7) by loading d3d12.dll dynamically. // 2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning. // 2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID. @@ -44,15 +52,27 @@ #endif // DirectX data -static ID3D12Device* g_pd3dDevice = NULL; -static ID3D12RootSignature* g_pRootSignature = NULL; -static ID3D12PipelineState* g_pPipelineState = NULL; -static DXGI_FORMAT g_RTVFormat = DXGI_FORMAT_UNKNOWN; -static ID3D12Resource* g_pFontTextureResource = NULL; -static D3D12_CPU_DESCRIPTOR_HANDLE g_hFontSrvCpuDescHandle = {}; -static D3D12_GPU_DESCRIPTOR_HANDLE g_hFontSrvGpuDescHandle = {}; -static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = NULL; -static UINT g_numFramesInFlight = 0; +struct ImGui_ImplDX12_Data +{ + ID3D12Device* pd3dDevice; + ID3D12RootSignature* pRootSignature; + ID3D12PipelineState* pPipelineState; + DXGI_FORMAT RTVFormat; + ID3D12Resource* pFontTextureResource; + D3D12_CPU_DESCRIPTOR_HANDLE hFontSrvCpuDescHandle; + D3D12_GPU_DESCRIPTOR_HANDLE hFontSrvGpuDescHandle; + ID3D12DescriptorHeap* pd3dSrvDescHeap; + UINT numFramesInFlight; + + ImGui_ImplDX12_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} // Buffers used during the rendering of a frame struct ImGui_ImplDX12_RenderBuffers @@ -74,7 +94,7 @@ struct ImGui_ImplDX12_FrameContext // Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data. // Main viewport created by application will only use the Resources field. // Secondary viewports created by this backend will use all the fields (including Window fields), -struct ImGuiViewportDataDx12 +struct ImGui_ImplDX12_ViewportData { // Window ID3D12CommandQueue* CommandQueue; @@ -84,13 +104,14 @@ struct ImGuiViewportDataDx12 ID3D12Fence* Fence; UINT64 FenceSignaledValue; HANDLE FenceEvent; + UINT NumFramesInFlight; ImGui_ImplDX12_FrameContext* FrameCtx; // Render buffers UINT FrameIndex; ImGui_ImplDX12_RenderBuffers* FrameRenderBuffers; - ImGuiViewportDataDx12() + ImGui_ImplDX12_ViewportData(UINT num_frames_in_flight) { CommandQueue = NULL; CommandList = NULL; @@ -99,11 +120,12 @@ struct ImGuiViewportDataDx12 Fence = NULL; FenceSignaledValue = 0; FenceEvent = NULL; - FrameCtx = new ImGui_ImplDX12_FrameContext[g_numFramesInFlight]; + NumFramesInFlight = num_frames_in_flight; + FrameCtx = new ImGui_ImplDX12_FrameContext[NumFramesInFlight]; FrameIndex = UINT_MAX; - FrameRenderBuffers = new ImGui_ImplDX12_RenderBuffers[g_numFramesInFlight]; + FrameRenderBuffers = new ImGui_ImplDX12_RenderBuffers[NumFramesInFlight]; - for (UINT i = 0; i < g_numFramesInFlight; ++i) + for (UINT i = 0; i < NumFramesInFlight; ++i) { FrameCtx[i].CommandAllocator = NULL; FrameCtx[i].RenderTarget = NULL; @@ -115,7 +137,7 @@ struct ImGuiViewportDataDx12 FrameRenderBuffers[i].IndexBufferSize = 10000; } } - ~ImGuiViewportDataDx12() + ~ImGui_ImplDX12_ViewportData() { IM_ASSERT(CommandQueue == NULL && CommandList == NULL); IM_ASSERT(RtvDescHeap == NULL); @@ -123,7 +145,7 @@ struct ImGuiViewportDataDx12 IM_ASSERT(Fence == NULL); IM_ASSERT(FenceEvent == NULL); - for (UINT i = 0; i < g_numFramesInFlight; ++i) + for (UINT i = 0; i < NumFramesInFlight; ++i) { IM_ASSERT(FrameCtx[i].CommandAllocator == NULL && FrameCtx[i].RenderTarget == NULL); IM_ASSERT(FrameRenderBuffers[i].IndexBuffer == NULL && FrameRenderBuffers[i].VertexBuffer == NULL); @@ -134,21 +156,6 @@ struct ImGuiViewportDataDx12 } }; -template -static void SafeRelease(T*& res) -{ - if (res) - res->Release(); - res = NULL; -} - -static void ImGui_ImplDX12_DestroyRenderBuffers(ImGui_ImplDX12_RenderBuffers* render_buffers) -{ - SafeRelease(render_buffers->IndexBuffer); - SafeRelease(render_buffers->VertexBuffer); - render_buffers->IndexBufferSize = render_buffers->VertexBufferSize = 0; -} - struct VERTEX_CONSTANT_BUFFER { float mvp[4][4]; @@ -158,8 +165,11 @@ struct VERTEX_CONSTANT_BUFFER static void ImGui_ImplDX12_InitPlatformInterface(); static void ImGui_ImplDX12_ShutdownPlatformInterface(); +// Functions static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx, ImGui_ImplDX12_RenderBuffers* fr) { + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + // Setup orthographic projection matrix into our constant buffer // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). VERTEX_CONSTANT_BUFFER vertex_constant_buffer; @@ -204,8 +214,8 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic ibv.Format = sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; ctx->IASetIndexBuffer(&ibv); ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ctx->SetPipelineState(g_pPipelineState); - ctx->SetGraphicsRootSignature(g_pRootSignature); + ctx->SetPipelineState(bd->pPipelineState); + ctx->SetGraphicsRootSignature(bd->pRootSignature); ctx->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0); // Setup blend factor @@ -213,6 +223,14 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic ctx->OMSetBlendFactor(blend_factor); } +template +static inline void SafeRelease(T*& res) +{ + if (res) + res->Release(); + res = NULL; +} + // Render function void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx) { @@ -220,9 +238,10 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; - ImGuiViewportDataDx12* render_data = (ImGuiViewportDataDx12*)draw_data->OwnerViewport->RendererUserData; - render_data->FrameIndex++; - ImGui_ImplDX12_RenderBuffers* fr = &render_data->FrameRenderBuffers[render_data->FrameIndex % g_numFramesInFlight]; + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)draw_data->OwnerViewport->RendererUserData; + vd->FrameIndex++; + ImGui_ImplDX12_RenderBuffers* fr = &vd->FrameRenderBuffers[vd->FrameIndex % bd->numFramesInFlight]; // Create and grow vertex/index buffers if needed if (fr->VertexBuffer == NULL || fr->VertexBufferSize < draw_data->TotalVtxCount) @@ -245,7 +264,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL desc.SampleDesc.Count = 1; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_NONE; - if (g_pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&fr->VertexBuffer)) < 0) + if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&fr->VertexBuffer)) < 0) return; } if (fr->IndexBuffer == NULL || fr->IndexBufferSize < draw_data->TotalIdxCount) @@ -268,7 +287,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL desc.SampleDesc.Count = 1; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_NONE; - if (g_pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&fr->IndexBuffer)) < 0) + if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&fr->IndexBuffer)) < 0) return; } @@ -322,7 +341,9 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL const D3D12_RECT r = { (LONG)(pcmd->ClipRect.x - clip_off.x), (LONG)(pcmd->ClipRect.y - clip_off.y), (LONG)(pcmd->ClipRect.z - clip_off.x), (LONG)(pcmd->ClipRect.w - clip_off.y) }; if (r.right > r.left && r.bottom > r.top) { - ctx->SetGraphicsRootDescriptorTable(1, *(D3D12_GPU_DESCRIPTOR_HANDLE*)&pcmd->TextureId); + D3D12_GPU_DESCRIPTOR_HANDLE texture_handle = {}; + texture_handle.ptr = (UINT64)pcmd->GetTexID(); + ctx->SetGraphicsRootDescriptorTable(1, texture_handle); ctx->RSSetScissorRects(1, &r); ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); } @@ -337,6 +358,7 @@ static void ImGui_ImplDX12_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); @@ -364,7 +386,7 @@ static void ImGui_ImplDX12_CreateFontsTexture() desc.Flags = D3D12_RESOURCE_FLAG_NONE; ID3D12Resource* pTexture = NULL; - g_pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, + bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&pTexture)); UINT uploadPitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); @@ -386,7 +408,7 @@ static void ImGui_ImplDX12_CreateFontsTexture() props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; ID3D12Resource* uploadBuffer = NULL; - HRESULT hr = g_pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, + HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&uploadBuffer)); IM_ASSERT(SUCCEEDED(hr)); @@ -421,7 +443,7 @@ static void ImGui_ImplDX12_CreateFontsTexture() barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; ID3D12Fence* fence = NULL; - hr = g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); + hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); IM_ASSERT(SUCCEEDED(hr)); HANDLE event = CreateEvent(0, 0, 0, 0); @@ -433,15 +455,15 @@ static void ImGui_ImplDX12_CreateFontsTexture() queueDesc.NodeMask = 1; ID3D12CommandQueue* cmdQueue = NULL; - hr = g_pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue)); + hr = bd->pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue)); IM_ASSERT(SUCCEEDED(hr)); ID3D12CommandAllocator* cmdAlloc = NULL; - hr = g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); + hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); IM_ASSERT(SUCCEEDED(hr)); ID3D12GraphicsCommandList* cmdList = NULL; - hr = g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, NULL, IID_PPV_ARGS(&cmdList)); + hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, NULL, IID_PPV_ARGS(&cmdList)); IM_ASSERT(SUCCEEDED(hr)); cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, NULL); @@ -472,21 +494,29 @@ static void ImGui_ImplDX12_CreateFontsTexture() srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, g_hFontSrvCpuDescHandle); - SafeRelease(g_pFontTextureResource); - g_pFontTextureResource = pTexture; + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, bd->hFontSrvCpuDescHandle); + SafeRelease(bd->pFontTextureResource); + bd->pFontTextureResource = pTexture; } // Store our identifier - static_assert(sizeof(ImTextureID) >= sizeof(g_hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet."); - io.Fonts->SetTexID((ImTextureID)g_hFontSrvGpuDescHandle.ptr); + // READ THIS IF THE STATIC_ASSERT() TRIGGERS: + // - Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. + // - This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. + // [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file) + // [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. + // [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) + // [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file) + static_assert(sizeof(ImTextureID) >= sizeof(bd->hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet."); + io.Fonts->SetTexID((ImTextureID)bd->hFontSrvGpuDescHandle.ptr); } bool ImGui_ImplDX12_CreateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + if (!bd || !bd->pd3dDevice) return false; - if (g_pPipelineState) + if (bd->pPipelineState) ImGui_ImplDX12_InvalidateDeviceObjects(); // Create the root signature @@ -567,7 +597,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() if (D3D12SerializeRootSignatureFn(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, NULL) != S_OK) return false; - g_pd3dDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&g_pRootSignature)); + bd->pd3dDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&bd->pRootSignature)); blob->Release(); } @@ -581,10 +611,10 @@ bool ImGui_ImplDX12_CreateDeviceObjects() memset(&psoDesc, 0, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC)); psoDesc.NodeMask = 1; psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - psoDesc.pRootSignature = g_pRootSignature; + psoDesc.pRootSignature = bd->pRootSignature; psoDesc.SampleMask = UINT_MAX; psoDesc.NumRenderTargets = 1; - psoDesc.RTVFormats[0] = g_RTVFormat; + psoDesc.RTVFormats[0] = bd->RTVFormat; psoDesc.SampleDesc.Count = 1; psoDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE; @@ -669,8 +699,8 @@ bool ImGui_ImplDX12_CreateDeviceObjects() desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO; + desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; } @@ -703,7 +733,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() desc.BackFace = desc.FrontFace; } - HRESULT result_pipeline_state = g_pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&g_pPipelineState)); + HRESULT result_pipeline_state = bd->pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&bd->pPipelineState)); vertexShaderBlob->Release(); pixelShaderBlob->Release(); if (result_pipeline_state != S_OK) @@ -714,58 +744,69 @@ bool ImGui_ImplDX12_CreateDeviceObjects() return true; } +static void ImGui_ImplDX12_DestroyRenderBuffers(ImGui_ImplDX12_RenderBuffers* render_buffers) +{ + SafeRelease(render_buffers->IndexBuffer); + SafeRelease(render_buffers->VertexBuffer); + render_buffers->IndexBufferSize = render_buffers->VertexBufferSize = 0; +} + void ImGui_ImplDX12_InvalidateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + if (!bd || !bd->pd3dDevice) return; - SafeRelease(g_pRootSignature); - SafeRelease(g_pPipelineState); - SafeRelease(g_pFontTextureResource); - ImGuiIO& io = ImGui::GetIO(); - io.Fonts->SetTexID(NULL); // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. + SafeRelease(bd->pRootSignature); + SafeRelease(bd->pPipelineState); + SafeRelease(bd->pFontTextureResource); + io.Fonts->SetTexID(NULL); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. } bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle) { - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx12"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) // FIXME-VIEWPORT: Actually unfinished.. + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplDX12_InitPlatformInterface(); - g_pd3dDevice = device; - g_RTVFormat = rtv_format; - g_hFontSrvCpuDescHandle = font_srv_cpu_desc_handle; - g_hFontSrvGpuDescHandle = font_srv_gpu_desc_handle; - g_numFramesInFlight = num_frames_in_flight; - g_pd3dSrvDescHeap = cbv_srv_heap; + bd->pd3dDevice = device; + bd->RTVFormat = rtv_format; + bd->hFontSrvCpuDescHandle = font_srv_cpu_desc_handle; + bd->hFontSrvGpuDescHandle = font_srv_gpu_desc_handle; + bd->numFramesInFlight = num_frames_in_flight; + bd->pd3dSrvDescHeap = cbv_srv_heap; - // Create a dummy ImGuiViewportDataDx12 holder for the main viewport, + // Create a dummy ImGui_ImplDX12_ViewportData holder for the main viewport, // Since this is created and managed by the application, we will only use the ->Resources[] fields. ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - main_viewport->RendererUserData = IM_NEW(ImGuiViewportDataDx12)(); - - // Setup backend capabilities flags - io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - ImGui_ImplDX12_InitPlatformInterface(); + main_viewport->RendererUserData = IM_NEW(ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight); return true; } void ImGui_ImplDX12_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + // Manually delete main viewport render resources in-case we haven't initialized for viewports ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - if (ImGuiViewportDataDx12* data = (ImGuiViewportDataDx12*)main_viewport->RendererUserData) + if (ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)main_viewport->RendererUserData) { // We could just call ImGui_ImplDX12_DestroyWindow(main_viewport) as a convenience but that would be misleading since we only use data->Resources[] - for (UINT i = 0; i < g_numFramesInFlight; i++) - ImGui_ImplDX12_DestroyRenderBuffers(&data->FrameRenderBuffers[i]); - IM_DELETE(data); + for (UINT i = 0; i < bd->numFramesInFlight; i++) + ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); + IM_DELETE(vd); main_viewport->RendererUserData = NULL; } @@ -773,16 +814,17 @@ void ImGui_ImplDX12_Shutdown() ImGui_ImplDX12_ShutdownPlatformInterface(); ImGui_ImplDX12_InvalidateDeviceObjects(); - g_pd3dDevice = NULL; - g_hFontSrvCpuDescHandle.ptr = 0; - g_hFontSrvGpuDescHandle.ptr = 0; - g_numFramesInFlight = 0; - g_pd3dSrvDescHeap = NULL; + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplDX12_NewFrame() { - if (!g_pPipelineState) + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplDX12_Init()?"); + + if (!bd->pPipelineState) ImGui_ImplDX12_CreateDeviceObjects(); } @@ -794,15 +836,16 @@ void ImGui_ImplDX12_NewFrame() static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataDx12* data = IM_NEW(ImGuiViewportDataDx12)(); - viewport->RendererUserData = data; + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + ImGui_ImplDX12_ViewportData* vd = IM_NEW(ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight); + viewport->RendererUserData = vd; // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). // Some backends will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle; IM_ASSERT(hwnd != 0); - data->FrameIndex = UINT_MAX; + vd->FrameIndex = UINT_MAX; // Create command queue. D3D12_COMMAND_QUEUE_DESC queue_desc = {}; @@ -810,36 +853,36 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; HRESULT res = S_OK; - res = g_pd3dDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&data->CommandQueue)); + res = bd->pd3dDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&vd->CommandQueue)); IM_ASSERT(res == S_OK); // Create command allocator. - for (UINT i = 0; i < g_numFramesInFlight; ++i) + for (UINT i = 0; i < bd->numFramesInFlight; ++i) { - res = g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&data->FrameCtx[i].CommandAllocator)); + res = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&vd->FrameCtx[i].CommandAllocator)); IM_ASSERT(res == S_OK); } // Create command list. - res = g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, data->FrameCtx[0].CommandAllocator, NULL, IID_PPV_ARGS(&data->CommandList)); + res = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, vd->FrameCtx[0].CommandAllocator, NULL, IID_PPV_ARGS(&vd->CommandList)); IM_ASSERT(res == S_OK); - data->CommandList->Close(); + vd->CommandList->Close(); // Create fence. - res = g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&data->Fence)); + res = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vd->Fence)); IM_ASSERT(res == S_OK); - data->FenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - IM_ASSERT(data->FenceEvent != NULL); + vd->FenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + IM_ASSERT(vd->FenceEvent != NULL); // Create swap chain // FIXME-VIEWPORT: May want to copy/inherit swap chain settings from the user/application. DXGI_SWAP_CHAIN_DESC1 sd1; ZeroMemory(&sd1, sizeof(sd1)); - sd1.BufferCount = g_numFramesInFlight; + sd1.BufferCount = bd->numFramesInFlight; sd1.Width = (UINT)viewport->Size.x; sd1.Height = (UINT)viewport->Size.y; - sd1.Format = g_RTVFormat; + sd1.Format = bd->RTVFormat; sd1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd1.SampleDesc.Count = 1; sd1.SampleDesc.Quality = 0; @@ -853,138 +896,141 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) IM_ASSERT(res == S_OK); IDXGISwapChain1* swap_chain = NULL; - res = dxgi_factory->CreateSwapChainForHwnd(data->CommandQueue, hwnd, &sd1, NULL, NULL, &swap_chain); + res = dxgi_factory->CreateSwapChainForHwnd(vd->CommandQueue, hwnd, &sd1, NULL, NULL, &swap_chain); IM_ASSERT(res == S_OK); dxgi_factory->Release(); // Or swapChain.As(&mSwapChain) - IM_ASSERT(data->SwapChain == NULL); - swap_chain->QueryInterface(IID_PPV_ARGS(&data->SwapChain)); + IM_ASSERT(vd->SwapChain == NULL); + swap_chain->QueryInterface(IID_PPV_ARGS(&vd->SwapChain)); swap_chain->Release(); // Create the render targets - if (data->SwapChain) + if (vd->SwapChain) { D3D12_DESCRIPTOR_HEAP_DESC desc = {}; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; - desc.NumDescriptors = g_numFramesInFlight; + desc.NumDescriptors = bd->numFramesInFlight; desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; desc.NodeMask = 1; - HRESULT hr = g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&data->RtvDescHeap)); + HRESULT hr = bd->pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&vd->RtvDescHeap)); IM_ASSERT(hr == S_OK); - SIZE_T rtv_descriptor_size = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = data->RtvDescHeap->GetCPUDescriptorHandleForHeapStart(); - for (UINT i = 0; i < g_numFramesInFlight; i++) + SIZE_T rtv_descriptor_size = bd->pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = vd->RtvDescHeap->GetCPUDescriptorHandleForHeapStart(); + for (UINT i = 0; i < bd->numFramesInFlight; i++) { - data->FrameCtx[i].RenderTargetCpuDescriptors = rtv_handle; + vd->FrameCtx[i].RenderTargetCpuDescriptors = rtv_handle; rtv_handle.ptr += rtv_descriptor_size; } ID3D12Resource* back_buffer; - for (UINT i = 0; i < g_numFramesInFlight; i++) + for (UINT i = 0; i < bd->numFramesInFlight; i++) { - IM_ASSERT(data->FrameCtx[i].RenderTarget == NULL); - data->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); - g_pd3dDevice->CreateRenderTargetView(back_buffer, NULL, data->FrameCtx[i].RenderTargetCpuDescriptors); - data->FrameCtx[i].RenderTarget = back_buffer; + IM_ASSERT(vd->FrameCtx[i].RenderTarget == NULL); + vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); + bd->pd3dDevice->CreateRenderTargetView(back_buffer, NULL, vd->FrameCtx[i].RenderTargetCpuDescriptors); + vd->FrameCtx[i].RenderTarget = back_buffer; } } - for (UINT i = 0; i < g_numFramesInFlight; i++) - ImGui_ImplDX12_DestroyRenderBuffers(&data->FrameRenderBuffers[i]); + for (UINT i = 0; i < bd->numFramesInFlight; i++) + ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); } -static void ImGui_WaitForPendingOperations(ImGuiViewportDataDx12* data) +static void ImGui_WaitForPendingOperations(ImGui_ImplDX12_ViewportData* vd) { HRESULT hr = S_FALSE; - if (data && data->CommandQueue && data->Fence && data->FenceEvent) + if (vd && vd->CommandQueue && vd->Fence && vd->FenceEvent) { - hr = data->CommandQueue->Signal(data->Fence, ++data->FenceSignaledValue); + hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceSignaledValue); IM_ASSERT(hr == S_OK); - ::WaitForSingleObject(data->FenceEvent, 0); // Reset any forgotten waits - hr = data->Fence->SetEventOnCompletion(data->FenceSignaledValue, data->FenceEvent); + ::WaitForSingleObject(vd->FenceEvent, 0); // Reset any forgotten waits + hr = vd->Fence->SetEventOnCompletion(vd->FenceSignaledValue, vd->FenceEvent); IM_ASSERT(hr == S_OK); - ::WaitForSingleObject(data->FenceEvent, INFINITE); + ::WaitForSingleObject(vd->FenceEvent, INFINITE); } } static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. - if (ImGuiViewportDataDx12* data = (ImGuiViewportDataDx12*)viewport->RendererUserData) + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + if (ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData) { - ImGui_WaitForPendingOperations(data); + ImGui_WaitForPendingOperations(vd); - SafeRelease(data->CommandQueue); - SafeRelease(data->CommandList); - SafeRelease(data->SwapChain); - SafeRelease(data->RtvDescHeap); - SafeRelease(data->Fence); - ::CloseHandle(data->FenceEvent); - data->FenceEvent = NULL; + SafeRelease(vd->CommandQueue); + SafeRelease(vd->CommandList); + SafeRelease(vd->SwapChain); + SafeRelease(vd->RtvDescHeap); + SafeRelease(vd->Fence); + ::CloseHandle(vd->FenceEvent); + vd->FenceEvent = NULL; - for (UINT i = 0; i < g_numFramesInFlight; i++) + for (UINT i = 0; i < bd->numFramesInFlight; i++) { - SafeRelease(data->FrameCtx[i].RenderTarget); - SafeRelease(data->FrameCtx[i].CommandAllocator); - ImGui_ImplDX12_DestroyRenderBuffers(&data->FrameRenderBuffers[i]); + SafeRelease(vd->FrameCtx[i].RenderTarget); + SafeRelease(vd->FrameCtx[i].CommandAllocator); + ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); } - IM_DELETE(data); + IM_DELETE(vd); } viewport->RendererUserData = NULL; } static void ImGui_ImplDX12_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataDx12* data = (ImGuiViewportDataDx12*)viewport->RendererUserData; + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - ImGui_WaitForPendingOperations(data); + ImGui_WaitForPendingOperations(vd); - for (UINT i = 0; i < g_numFramesInFlight; i++) - SafeRelease(data->FrameCtx[i].RenderTarget); + for (UINT i = 0; i < bd->numFramesInFlight; i++) + SafeRelease(vd->FrameCtx[i].RenderTarget); - if (data->SwapChain) + if (vd->SwapChain) { ID3D12Resource* back_buffer = NULL; - data->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); - for (UINT i = 0; i < g_numFramesInFlight; i++) + vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); + for (UINT i = 0; i < bd->numFramesInFlight; i++) { - data->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); - g_pd3dDevice->CreateRenderTargetView(back_buffer, NULL, data->FrameCtx[i].RenderTargetCpuDescriptors); - data->FrameCtx[i].RenderTarget = back_buffer; + vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); + bd->pd3dDevice->CreateRenderTargetView(back_buffer, NULL, vd->FrameCtx[i].RenderTargetCpuDescriptors); + vd->FrameCtx[i].RenderTarget = back_buffer; } } } static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx12* data = (ImGuiViewportDataDx12*)viewport->RendererUserData; + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - ImGui_ImplDX12_FrameContext* frame_context = &data->FrameCtx[data->FrameIndex % g_numFramesInFlight]; - UINT back_buffer_idx = data->SwapChain->GetCurrentBackBufferIndex(); + ImGui_ImplDX12_FrameContext* frame_context = &vd->FrameCtx[vd->FrameIndex % bd->numFramesInFlight]; + UINT back_buffer_idx = vd->SwapChain->GetCurrentBackBufferIndex(); const ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); D3D12_RESOURCE_BARRIER barrier = {}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = data->FrameCtx[back_buffer_idx].RenderTarget; + barrier.Transition.pResource = vd->FrameCtx[back_buffer_idx].RenderTarget; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; // Draw - ID3D12GraphicsCommandList* cmd_list = data->CommandList; + ID3D12GraphicsCommandList* cmd_list = vd->CommandList; frame_context->CommandAllocator->Reset(); cmd_list->Reset(frame_context->CommandAllocator, NULL); cmd_list->ResourceBarrier(1, &barrier); - cmd_list->OMSetRenderTargets(1, &data->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, FALSE, NULL); + cmd_list->OMSetRenderTargets(1, &vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, FALSE, NULL); if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) - cmd_list->ClearRenderTargetView(data->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, (float*)&clear_color, 0, NULL); - cmd_list->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap); + cmd_list->ClearRenderTargetView(vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, (float*)&clear_color, 0, NULL); + cmd_list->SetDescriptorHeaps(1, &bd->pd3dSrvDescHeap); ImGui_ImplDX12_RenderDrawData(viewport->DrawData, cmd_list); @@ -993,17 +1039,17 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) cmd_list->ResourceBarrier(1, &barrier); cmd_list->Close(); - data->CommandQueue->Wait(data->Fence, data->FenceSignaledValue); - data->CommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmd_list); - data->CommandQueue->Signal(data->Fence, ++data->FenceSignaledValue); + vd->CommandQueue->Wait(vd->Fence, vd->FenceSignaledValue); + vd->CommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmd_list); + vd->CommandQueue->Signal(vd->Fence, ++vd->FenceSignaledValue); } static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx12* data = (ImGuiViewportDataDx12*)viewport->RendererUserData; + ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - data->SwapChain->Present(0, 0); - while (data->Fence->GetCompletedValue() < data->FenceSignaledValue) + vd->SwapChain->Present(0, 0); + while (vd->Fence->GetCompletedValue() < vd->FenceSignaledValue) ::SwitchToThread(); } diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx12.h b/examples/interactive/imgui-1.83/backends/imgui_impl_dx12.h similarity index 91% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx12.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx12.h index d6657a13..774dab38 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx12.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx12.h @@ -10,7 +10,8 @@ // This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. // This define is set in the example .vcxproj file and need to be replicated in your app or by adding it to your imconfig.h file. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx9.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_dx9.cpp similarity index 51% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx9.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx9.cpp index c3eb2c69..a4d67a42 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx9.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx9.cpp @@ -6,13 +6,21 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1. +// 2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states. +// 2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857). +// 2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file. +// 2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer. // 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects(). @@ -29,15 +37,19 @@ // DirectX #include -#define DIRECTINPUT_VERSION 0x0800 -#include // DirectX data -static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; -static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; -static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL; -static LPDIRECT3DTEXTURE9 g_FontTexture = NULL; -static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000; +struct ImGui_ImplDX9_Data +{ + LPDIRECT3DDEVICE9 pd3dDevice; + LPDIRECT3DVERTEXBUFFER9 pVB; + LPDIRECT3DINDEXBUFFER9 pIB; + LPDIRECT3DTEXTURE9 FontTexture; + int VertexBufferSize; + int IndexBufferSize; + + ImGui_ImplDX9_Data() { memset(this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } +}; struct CUSTOMVERTEX { @@ -47,14 +59,30 @@ struct CUSTOMVERTEX }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL) +#else +#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16)) +#endif + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + // Forward Declarations static void ImGui_ImplDX9_InitPlatformInterface(); static void ImGui_ImplDX9_ShutdownPlatformInterface(); static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows(); static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows(); +// Functions static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) { + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + // Setup viewport D3DVIEWPORT9 vp; vp.X = vp.Y = 0; @@ -62,30 +90,41 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) vp.Height = (DWORD)draw_data->DisplaySize.y; vp.MinZ = 0.0f; vp.MaxZ = 1.0f; - g_pd3dDevice->SetViewport(&vp); + bd->pd3dDevice->SetViewport(&vp); // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient) - g_pd3dDevice->SetPixelShader(NULL); - g_pd3dDevice->SetVertexShader(NULL); - g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); - g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); - g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); - g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); - g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + bd->pd3dDevice->SetPixelShader(NULL); + bd->pd3dDevice->SetVertexShader(NULL); + bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); + bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE); + bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA); + bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); + bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE); + bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); // Setup orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. @@ -103,9 +142,9 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) 0.0f, 0.0f, 0.5f, 0.0f, (L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f } } }; - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); + bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); + bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); + bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); } } @@ -117,42 +156,57 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) return; // Create and grow buffers if needed - if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - g_VertexBufferSize = draw_data->TotalVtxCount + 5000; - if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; + if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, NULL) < 0) return; } - if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) + if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) { - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - g_IndexBufferSize = draw_data->TotalIdxCount + 10000; - if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &g_pIB, NULL) < 0) + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; + if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, NULL) < 0) return; } // Backup the DX9 state IDirect3DStateBlock9* d3d9_state_block = NULL; - if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) + if (bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) + return; + if (d3d9_state_block->Capture() < 0) + { + d3d9_state_block->Release(); return; + } // Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to) D3DMATRIX last_world, last_view, last_projection; - g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); - g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); - g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection); + bd->pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); + bd->pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); + bd->pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection); - // Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format. - // FIXME-OPT: This is a waste of resource, the ideal is to use imconfig.h and - // 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR - // 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } + // Allocate buffers CUSTOMVERTEX* vtx_dst; ImDrawIdx* idx_dst; - if (g_pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) + if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) + { + d3d9_state_block->Release(); return; - if (g_pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0) + } + if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0) + { + bd->pVB->Unlock(); + d3d9_state_block->Release(); return; + } + + // Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format. + // FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and + // 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR + // 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; @@ -162,7 +216,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) vtx_dst->pos[0] = vtx_src->pos.x; vtx_dst->pos[1] = vtx_src->pos.y; vtx_dst->pos[2] = 0.0f; - vtx_dst->col = (vtx_src->col & 0xFF00FF00) | ((vtx_src->col & 0xFF0000) >> 16) | ((vtx_src->col & 0xFF) << 16); // RGBA --> ARGB for DirectX9 + vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col); vtx_dst->uv[0] = vtx_src->uv.x; vtx_dst->uv[1] = vtx_src->uv.y; vtx_dst++; @@ -171,11 +225,11 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); idx_dst += cmd_list->IdxBuffer.Size; } - g_pVB->Unlock(); - g_pIB->Unlock(); - g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX)); - g_pd3dDevice->SetIndices(g_pIB); - g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); + bd->pVB->Unlock(); + bd->pIB->Unlock(); + bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX)); + bd->pd3dDevice->SetIndices(bd->pIB); + bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); // Setup desired DX state ImGui_ImplDX9_SetupRenderState(draw_data); @@ -203,10 +257,10 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) else { const RECT r = { (LONG)(pcmd->ClipRect.x - clip_off.x), (LONG)(pcmd->ClipRect.y - clip_off.y), (LONG)(pcmd->ClipRect.z - clip_off.x), (LONG)(pcmd->ClipRect.w - clip_off.y) }; - const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->TextureId; - g_pd3dDevice->SetTexture(0, texture); - g_pd3dDevice->SetScissorRect(&r); - g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); + const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID(); + bd->pd3dDevice->SetTexture(0, texture); + bd->pd3dDevice->SetScissorRect(&r); + bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); } } global_idx_offset += cmd_list->IdxBuffer.Size; @@ -216,12 +270,12 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) // When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows // from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560) if (global_vtx_offset == 0) - g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0); + bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0); // Restore the DX9 transform - g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); + bd->pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); + bd->pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); + bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); // Restore the DX9 state d3d9_state_block->Apply(); @@ -230,14 +284,18 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) { - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx9"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) - g_pd3dDevice = device; - g_pd3dDevice->AddRef(); + bd->pd3dDevice = device; + bd->pd3dDevice->AddRef(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplDX9_InitPlatformInterface(); @@ -247,39 +305,63 @@ bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) void ImGui_ImplDX9_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + ImGui_ImplDX9_ShutdownPlatformInterface(); ImGui_ImplDX9_InvalidateDeviceObjects(); - if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; } + if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } static bool ImGui_ImplDX9_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); unsigned char* pixels; int width, height, bytes_per_pixel; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); + // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + if (io.Fonts->TexPixelsUseColors) + { + ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel); + for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++) + *dst = IMGUI_COL_TO_DX9_ARGB(*src); + pixels = (unsigned char*)dst_start; + } +#endif + // Upload texture to graphics system - g_FontTexture = NULL; - if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0) + bd->FontTexture = NULL; + if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, NULL) < 0) return false; D3DLOCKED_RECT tex_locked_rect; - if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) + if (bd->FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) return false; for (int y = 0; y < height; y++) - memcpy((unsigned char*)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); - g_FontTexture->UnlockRect(0); + memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel); + bd->FontTexture->UnlockRect(0); // Store our identifier - io.Fonts->SetTexID((ImTextureID)g_FontTexture); + io.Fonts->SetTexID((ImTextureID)bd->FontTexture); + +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + if (io.Fonts->TexPixelsUseColors) + ImGui::MemFree(pixels); +#endif return true; } bool ImGui_ImplDX9_CreateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (!bd || !bd->pd3dDevice) return false; if (!ImGui_ImplDX9_CreateFontsTexture()) return false; @@ -289,17 +371,21 @@ bool ImGui_ImplDX9_CreateDeviceObjects() void ImGui_ImplDX9_InvalidateDeviceObjects() { - if (!g_pd3dDevice) + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (!bd || !bd->pd3dDevice) return; - if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } - if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } - if (g_FontTexture) { g_FontTexture->Release(); g_FontTexture = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. + if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows(); } void ImGui_ImplDX9_NewFrame() { - if (!g_FontTexture) + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplDX9_Init()?"); + + if (!bd->FontTexture) ImGui_ImplDX9_CreateDeviceObjects(); } @@ -310,94 +396,97 @@ void ImGui_ImplDX9_NewFrame() //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataDx9 +struct ImGui_ImplDX9_ViewportData { IDirect3DSwapChain9* SwapChain; D3DPRESENT_PARAMETERS d3dpp; - ImGuiViewportDataDx9() { SwapChain = NULL; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); } - ~ImGuiViewportDataDx9() { IM_ASSERT(SwapChain == NULL); } + ImGui_ImplDX9_ViewportData() { SwapChain = NULL; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); } + ~ImGui_ImplDX9_ViewportData() { IM_ASSERT(SwapChain == NULL); } }; static void ImGui_ImplDX9_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataDx9* data = IM_NEW(ImGuiViewportDataDx9)(); - viewport->RendererUserData = data; + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + ImGui_ImplDX9_ViewportData* vd = IM_NEW(ImGui_ImplDX9_ViewportData)(); + viewport->RendererUserData = vd; // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). // Some backends will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND. HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle; IM_ASSERT(hwnd != 0); - ZeroMemory(&data->d3dpp, sizeof(D3DPRESENT_PARAMETERS)); - data->d3dpp.Windowed = TRUE; - data->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; - data->d3dpp.BackBufferWidth = (UINT)viewport->Size.x; - data->d3dpp.BackBufferHeight = (UINT)viewport->Size.y; - data->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; - data->d3dpp.hDeviceWindow = hwnd; - data->d3dpp.EnableAutoDepthStencil = FALSE; - data->d3dpp.AutoDepthStencilFormat = D3DFMT_D16; - data->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync - - HRESULT hr = g_pd3dDevice->CreateAdditionalSwapChain(&data->d3dpp, &data->SwapChain); IM_UNUSED(hr); + ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS)); + vd->d3dpp.Windowed = TRUE; + vd->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + vd->d3dpp.BackBufferWidth = (UINT)viewport->Size.x; + vd->d3dpp.BackBufferHeight = (UINT)viewport->Size.y; + vd->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; + vd->d3dpp.hDeviceWindow = hwnd; + vd->d3dpp.EnableAutoDepthStencil = FALSE; + vd->d3dpp.AutoDepthStencilFormat = D3DFMT_D16; + vd->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync + + HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr); IM_ASSERT(hr == D3D_OK); - IM_ASSERT(data->SwapChain != NULL); + IM_ASSERT(vd->SwapChain != NULL); } static void ImGui_ImplDX9_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. - if (ImGuiViewportDataDx9* data = (ImGuiViewportDataDx9*)viewport->RendererUserData) + if (ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData) { - if (data->SwapChain) - data->SwapChain->Release(); - data->SwapChain = NULL; - ZeroMemory(&data->d3dpp, sizeof(D3DPRESENT_PARAMETERS)); - IM_DELETE(data); + if (vd->SwapChain) + vd->SwapChain->Release(); + vd->SwapChain = NULL; + ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS)); + IM_DELETE(vd); } viewport->RendererUserData = NULL; } static void ImGui_ImplDX9_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataDx9* data = (ImGuiViewportDataDx9*)viewport->RendererUserData; - if (data->SwapChain) + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData; + if (vd->SwapChain) { - data->SwapChain->Release(); - data->SwapChain = NULL; - data->d3dpp.BackBufferWidth = (UINT)size.x; - data->d3dpp.BackBufferHeight = (UINT)size.y; - HRESULT hr = g_pd3dDevice->CreateAdditionalSwapChain(&data->d3dpp, &data->SwapChain); IM_UNUSED(hr); + vd->SwapChain->Release(); + vd->SwapChain = NULL; + vd->d3dpp.BackBufferWidth = (UINT)size.x; + vd->d3dpp.BackBufferHeight = (UINT)size.y; + HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr); IM_ASSERT(hr == D3D_OK); } } static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx9* data = (ImGuiViewportDataDx9*)viewport->RendererUserData; + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData; ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); LPDIRECT3DSURFACE9 render_target = NULL; LPDIRECT3DSURFACE9 last_render_target = NULL; LPDIRECT3DSURFACE9 last_depth_stencil = NULL; - data->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target); - g_pd3dDevice->GetRenderTarget(0, &last_render_target); - g_pd3dDevice->GetDepthStencilSurface(&last_depth_stencil); - g_pd3dDevice->SetRenderTarget(0, render_target); - g_pd3dDevice->SetDepthStencilSurface(NULL); + vd->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target); + bd->pd3dDevice->GetRenderTarget(0, &last_render_target); + bd->pd3dDevice->GetDepthStencilSurface(&last_depth_stencil); + bd->pd3dDevice->SetRenderTarget(0, render_target); + bd->pd3dDevice->SetDepthStencilSurface(NULL); if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) { D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f)); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0); + bd->pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0); } ImGui_ImplDX9_RenderDrawData(viewport->DrawData); // Restore render target - g_pd3dDevice->SetRenderTarget(0, last_render_target); - g_pd3dDevice->SetDepthStencilSurface(last_depth_stencil); + bd->pd3dDevice->SetRenderTarget(0, last_render_target); + bd->pd3dDevice->SetDepthStencilSurface(last_depth_stencil); render_target->Release(); last_render_target->Release(); if (last_depth_stencil) last_depth_stencil->Release(); @@ -405,8 +494,8 @@ static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*) static void ImGui_ImplDX9_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataDx9* data = (ImGuiViewportDataDx9*)viewport->RendererUserData; - HRESULT hr = data->SwapChain->Present(NULL, NULL, data->d3dpp.hDeviceWindow, NULL, NULL); + ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData; + HRESULT hr = vd->SwapChain->Present(NULL, NULL, vd->d3dpp.hDeviceWindow, NULL, 0); // Let main application handle D3DERR_DEVICELOST by resetting the device. IM_ASSERT(hr == D3D_OK || hr == D3DERR_DEVICELOST); } diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_dx9.h b/examples/interactive/imgui-1.83/backends/imgui_impl_dx9.h similarity index 81% rename from examples/interactive/imgui-1.81/backends/imgui_impl_dx9.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_dx9.h index f04de4c1..773acda7 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_dx9.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_dx9.h @@ -6,7 +6,8 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_glfw.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_glfw.cpp similarity index 65% rename from examples/interactive/imgui-1.81/backends/imgui_impl_glfw.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_glfw.cpp index 3344b7bd..61e1e377 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_glfw.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_glfw.cpp @@ -13,13 +13,16 @@ // Issues: // [ ] Platform: Multi-viewport support: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-07-29: *BREAKING CHANGE*: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using glfwSetCursorEnterCallback). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install the glfwSetCursorEnterCallback() callback and the forward to the backend via ImGui_ImplGlfw_CursorEnterCallback(). +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). // 2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors. // 2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor). // 2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown. @@ -72,33 +75,55 @@ #define GLFW_HAS_MOUSE_PASSTHROUGH (0) #endif -// Data +// GLFW data enum GlfwClientApi { GlfwClientApi_Unknown, GlfwClientApi_OpenGL, GlfwClientApi_Vulkan }; -static GLFWwindow* g_Window = NULL; // Main window -static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown; -static double g_Time = 0.0; -static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {}; -static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; -static bool g_InstalledCallbacks = false; -static bool g_WantUpdateMonitors = true; - -// Chain GLFW callbacks for main viewport: our callbacks will call the user's previously installed callbacks, if any. -static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL; -static GLFWscrollfun g_PrevUserCallbackScroll = NULL; -static GLFWkeyfun g_PrevUserCallbackKey = NULL; -static GLFWcharfun g_PrevUserCallbackChar = NULL; -static GLFWmonitorfun g_PrevUserCallbackMonitor = NULL; + +struct ImGui_ImplGlfw_Data +{ + GLFWwindow* Window; + GlfwClientApi ClientApi; + double Time; + GLFWwindow* MouseWindow; + bool MouseJustPressed[ImGuiMouseButton_COUNT]; + GLFWcursor* MouseCursors[ImGuiMouseCursor_COUNT]; + GLFWwindow* KeyOwnerWindows[512]; + bool InstalledCallbacks; + bool WantUpdateMonitors; + + // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. + GLFWcursorenterfun PrevUserCallbackCursorEnter; + GLFWmousebuttonfun PrevUserCallbackMousebutton; + GLFWscrollfun PrevUserCallbackScroll; + GLFWkeyfun PrevUserCallbackKey; + GLFWcharfun PrevUserCallbackChar; + GLFWmonitorfun PrevUserCallbackMonitor; + + ImGui_ImplGlfw_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in this backend. +// - Because glfwPollEvents() process all windows and some events may be called outside of it, you will need to register your own callbacks +// (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks. +// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it. +// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. +static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; +} // Forward Declarations static void ImGui_ImplGlfw_UpdateMonitors(); static void ImGui_ImplGlfw_InitPlatformInterface(); static void ImGui_ImplGlfw_ShutdownPlatformInterface(); +// Functions static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data) { return glfwGetClipboardString((GLFWwindow*)user_data); @@ -111,17 +136,19 @@ static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text) void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - if (g_PrevUserCallbackMousebutton != NULL && window == g_Window) - g_PrevUserCallbackMousebutton(window, button, action, mods); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackMousebutton != NULL && window == bd->Window) + bd->PrevUserCallbackMousebutton(window, button, action, mods); - if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed)) - g_MouseJustPressed[button] = true; + if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(bd->MouseJustPressed)) + bd->MouseJustPressed[button] = true; } void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) { - if (g_PrevUserCallbackScroll != NULL && window == g_Window) - g_PrevUserCallbackScroll(window, xoffset, yoffset); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackScroll != NULL && window == bd->Window) + bd->PrevUserCallbackScroll(window, xoffset, yoffset); ImGuiIO& io = ImGui::GetIO(); io.MouseWheelH += (float)xoffset; @@ -130,14 +157,24 @@ void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yo void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (g_PrevUserCallbackKey != NULL && window == g_Window) - g_PrevUserCallbackKey(window, key, scancode, action, mods); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackKey != NULL && window == bd->Window) + bd->PrevUserCallbackKey(window, key, scancode, action, mods); ImGuiIO& io = ImGui::GetIO(); - if (action == GLFW_PRESS) - io.KeysDown[key] = true; - if (action == GLFW_RELEASE) - io.KeysDown[key] = false; + if (key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)) + { + if (action == GLFW_PRESS) + { + io.KeysDown[key] = true; + bd->KeyOwnerWindows[key] = window; + } + if (action == GLFW_RELEASE) + { + io.KeysDown[key] = false; + bd->KeyOwnerWindows[key] = NULL; + } + } // Modifiers are not reliable across systems io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; @@ -150,10 +187,22 @@ void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int a #endif } +void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered) +{ + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackCursorEnter != NULL) + bd->PrevUserCallbackCursorEnter(window, entered); + if (entered) + bd->MouseWindow = window; + if (!entered && bd->MouseWindow == window) + bd->MouseWindow = NULL; +} + void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c) { - if (g_PrevUserCallbackChar != NULL && window == g_Window) - g_PrevUserCallbackChar(window, c); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackChar != NULL && window == bd->Window) + bd->PrevUserCallbackChar(window, c); ImGuiIO& io = ImGui::GetIO(); io.AddInputCharacter(c); @@ -161,25 +210,31 @@ void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c) void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int) { - g_WantUpdateMonitors = true; + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + bd->WantUpdateMonitors = true; } static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api) { - g_Window = window; - g_Time = 0.0; + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); // Setup backend capabilities flags - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_glfw"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) #if GLFW_HAS_MOUSE_PASSTHROUGH || (GLFW_HAS_WINDOW_HOVERED && defined(_WIN32)) io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can set io.MouseHoveredViewport correctly (optional, not easy) #endif - io.BackendPlatformName = "imgui_impl_glfw"; - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + bd->Window = window; + bd->Time = 0.0; + bd->WantUpdateMonitors = true; + + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; @@ -205,45 +260,46 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText; io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText; - io.ClipboardUserData = g_Window; + io.ClipboardUserData = bd->Window; // Create mouse cursors // (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist, // GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting. // Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.) GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL); - g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); - g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); #if GLFW_HAS_NEW_CURSORS - g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR); - g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR); #else - g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); #endif glfwSetErrorCallback(prev_error_callback); // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. - g_PrevUserCallbackMousebutton = NULL; - g_PrevUserCallbackScroll = NULL; - g_PrevUserCallbackKey = NULL; - g_PrevUserCallbackChar = NULL; - g_PrevUserCallbackMonitor = NULL; + bd->PrevUserCallbackMousebutton = NULL; + bd->PrevUserCallbackScroll = NULL; + bd->PrevUserCallbackKey = NULL; + bd->PrevUserCallbackChar = NULL; + bd->PrevUserCallbackMonitor = NULL; if (install_callbacks) { - g_InstalledCallbacks = true; - g_PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); - g_PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); - g_PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); - g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); - g_PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback); + bd->InstalledCallbacks = true; + bd->PrevUserCallbackCursorEnter = glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback); + bd->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + bd->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + bd->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + bd->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); + bd->PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback); } // Update monitors the first time (note: monitor callback are broken in GLFW 3.2 and earlier, see github.com/glfw/glfw/issues/784) @@ -252,14 +308,14 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw // Our mouse update function expect PlatformHandle to be filled for the main viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - main_viewport->PlatformHandle = (void*)g_Window; + main_viewport->PlatformHandle = (void*)bd->Window; #ifdef _WIN32 - main_viewport->PlatformHandleRaw = glfwGetWin32Window(g_Window); + main_viewport->PlatformHandleRaw = glfwGetWin32Window(bd->Window); #endif if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplGlfw_InitPlatformInterface(); - g_ClientApi = client_api; + bd->ClientApi = client_api; return true; } @@ -280,77 +336,86 @@ bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks) void ImGui_ImplGlfw_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplGlfw_ShutdownPlatformInterface(); - if (g_InstalledCallbacks) + if (bd->InstalledCallbacks) { - glfwSetMouseButtonCallback(g_Window, g_PrevUserCallbackMousebutton); - glfwSetScrollCallback(g_Window, g_PrevUserCallbackScroll); - glfwSetKeyCallback(g_Window, g_PrevUserCallbackKey); - glfwSetCharCallback(g_Window, g_PrevUserCallbackChar); - g_InstalledCallbacks = false; + glfwSetCursorEnterCallback(bd->Window, bd->PrevUserCallbackCursorEnter); + glfwSetMouseButtonCallback(bd->Window, bd->PrevUserCallbackMousebutton); + glfwSetScrollCallback(bd->Window, bd->PrevUserCallbackScroll); + glfwSetKeyCallback(bd->Window, bd->PrevUserCallbackKey); + glfwSetCharCallback(bd->Window, bd->PrevUserCallbackChar); + glfwSetMonitorCallback(bd->PrevUserCallbackMonitor); } for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) - { - glfwDestroyCursor(g_MouseCursors[cursor_n]); - g_MouseCursors[cursor_n] = NULL; - } - g_ClientApi = GlfwClientApi_Unknown; + glfwDestroyCursor(bd->MouseCursors[cursor_n]); + + io.BackendPlatformName = NULL; + io.BackendPlatformUserData = NULL; + IM_DELETE(bd); } static void ImGui_ImplGlfw_UpdateMousePosAndButtons() { - // Update buttons + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + const ImVec2 mouse_pos_prev = io.MousePos; + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + io.MouseHoveredViewport = 0; + + // Update mouse buttons + // (if a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame) for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { - // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. - io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; - g_MouseJustPressed[i] = false; + io.MouseDown[i] = bd->MouseJustPressed[i] || glfwGetMouseButton(bd->Window, i) != 0; + bd->MouseJustPressed[i] = false; } - // Update mouse position - const ImVec2 mouse_pos_backup = io.MousePos; - io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); - io.MouseHoveredViewport = 0; - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); for (int n = 0; n < platform_io.Viewports.Size; n++) { ImGuiViewport* viewport = platform_io.Viewports[n]; GLFWwindow* window = (GLFWwindow*)viewport->PlatformHandle; - IM_ASSERT(window != NULL); + #ifdef __EMSCRIPTEN__ const bool focused = true; - IM_ASSERT(platform_io.Viewports.Size == 1); #else const bool focused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0; #endif + GLFWwindow* mouse_window = (bd->MouseWindow == window || focused) ? window : NULL; + + // Update mouse buttons if (focused) + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + io.MouseDown[i] |= glfwGetMouseButton(window, i) != 0; + + // Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + // (When multi-viewports are enabled, all Dear ImGui positions are same as OS positions) + if (io.WantSetMousePos && focused) + glfwSetCursorPos(window, (double)(mouse_pos_prev.x - viewport->Pos.x), (double)(mouse_pos_prev.y - viewport->Pos.y)); + + // Set Dear ImGui mouse position from OS position + if (mouse_window != NULL) { - if (io.WantSetMousePos) + double mouse_x, mouse_y; + glfwGetCursorPos(mouse_window, &mouse_x, &mouse_y); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { - glfwSetCursorPos(window, (double)(mouse_pos_backup.x - viewport->Pos.x), (double)(mouse_pos_backup.y - viewport->Pos.y)); + // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) + int window_x, window_y; + glfwGetWindowPos(window, &window_x, &window_y); + io.MousePos = ImVec2((float)mouse_x + window_x, (float)mouse_y + window_y); } else { - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) - int window_x, window_y; - glfwGetWindowPos(window, &window_x, &window_y); - io.MousePos = ImVec2((float)mouse_x + window_x, (float)mouse_y + window_y); - } - else - { - // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - } + // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); } - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) - io.MouseDown[i] |= glfwGetMouseButton(window, i) != 0; } // (Optional) When using multiple viewports: set io.MouseHoveredViewport to the viewport the OS mouse cursor is hovering. @@ -375,7 +440,8 @@ static void ImGui_ImplGlfw_UpdateMousePosAndButtons() static void ImGui_ImplGlfw_UpdateMouseCursor() { ImGuiIO& io = ImGui::GetIO(); - if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) return; ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); @@ -392,7 +458,7 @@ static void ImGui_ImplGlfw_UpdateMouseCursor() { // Show OS mouse cursor // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here. - glfwSetCursor(window, g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetCursor(window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } @@ -437,6 +503,7 @@ static void ImGui_ImplGlfw_UpdateGamepads() static void ImGui_ImplGlfw_UpdateMonitors() { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); int monitors_count = 0; GLFWmonitor** glfw_monitors = glfwGetMonitors(&monitors_count); @@ -466,29 +533,30 @@ static void ImGui_ImplGlfw_UpdateMonitors() #endif platform_io.Monitors.push_back(monitor); } - g_WantUpdateMonitors = false; + bd->WantUpdateMonitors = false; } void ImGui_ImplGlfw_NewFrame() { ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplGlfw_InitForXXX()?"); // Setup display size (every frame to accommodate for window resizing) int w, h; int display_w, display_h; - glfwGetWindowSize(g_Window, &w, &h); - glfwGetFramebufferSize(g_Window, &display_w, &display_h); + glfwGetWindowSize(bd->Window, &w, &h); + glfwGetFramebufferSize(bd->Window, &display_w, &display_h); io.DisplaySize = ImVec2((float)w, (float)h); if (w > 0 && h > 0) io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); - if (g_WantUpdateMonitors) + if (bd->WantUpdateMonitors) ImGui_ImplGlfw_UpdateMonitors(); // Setup time step double current_time = glfwGetTime(); - io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f); - g_Time = current_time; + io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); + bd->Time = current_time; ImGui_ImplGlfw_UpdateMousePosAndButtons(); ImGui_ImplGlfw_UpdateMouseCursor(); @@ -504,15 +572,15 @@ void ImGui_ImplGlfw_NewFrame() //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataGlfw +struct ImGui_ImplGlfw_ViewportData { GLFWwindow* Window; bool WindowOwned; int IgnoreWindowPosEventFrame; int IgnoreWindowSizeEventFrame; - ImGuiViewportDataGlfw() { Window = NULL; WindowOwned = false; IgnoreWindowSizeEventFrame = IgnoreWindowPosEventFrame = -1; } - ~ImGuiViewportDataGlfw() { IM_ASSERT(Window == NULL); } + ImGui_ImplGlfw_ViewportData() { Window = NULL; WindowOwned = false; IgnoreWindowSizeEventFrame = IgnoreWindowPosEventFrame = -1; } + ~ImGui_ImplGlfw_ViewportData() { IM_ASSERT(Window == NULL); } }; static void ImGui_ImplGlfw_WindowCloseCallback(GLFWwindow* window) @@ -531,9 +599,9 @@ static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int, int) { if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window)) { - if (ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData) + if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData) { - bool ignore_event = (ImGui::GetFrameCount() <= data->IgnoreWindowPosEventFrame + 1); + bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowPosEventFrame + 1); //data->IgnoreWindowPosEventFrame = -1; if (ignore_event) return; @@ -546,9 +614,9 @@ static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int) { if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window)) { - if (ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData) + if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData) { - bool ignore_event = (ImGui::GetFrameCount() <= data->IgnoreWindowSizeEventFrame + 1); + bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowSizeEventFrame + 1); //data->IgnoreWindowSizeEventFrame = -1; if (ignore_event) return; @@ -559,58 +627,68 @@ static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int) static void ImGui_ImplGlfw_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = IM_NEW(ImGuiViewportDataGlfw)(); - viewport->PlatformUserData = data; + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplGlfw_ViewportData* vd = IM_NEW(ImGui_ImplGlfw_ViewportData)(); + viewport->PlatformUserData = vd; // GLFW 3.2 unfortunately always set focus on glfwCreateWindow() if GLFW_VISIBLE is set, regardless of GLFW_FOCUSED // With GLFW 3.3, the hint GLFW_FOCUS_ON_SHOW fixes this problem glfwWindowHint(GLFW_VISIBLE, false); glfwWindowHint(GLFW_FOCUSED, false); #if GLFW_HAS_FOCUS_ON_SHOW - glfwWindowHint(GLFW_FOCUS_ON_SHOW, false); + glfwWindowHint(GLFW_FOCUS_ON_SHOW, false); #endif glfwWindowHint(GLFW_DECORATED, (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? false : true); #if GLFW_HAS_WINDOW_TOPMOST glfwWindowHint(GLFW_FLOATING, (viewport->Flags & ImGuiViewportFlags_TopMost) ? true : false); #endif - GLFWwindow* share_window = (g_ClientApi == GlfwClientApi_OpenGL) ? g_Window : NULL; - data->Window = glfwCreateWindow((int)viewport->Size.x, (int)viewport->Size.y, "No Title Yet", NULL, share_window); - data->WindowOwned = true; - viewport->PlatformHandle = (void*)data->Window; + GLFWwindow* share_window = (bd->ClientApi == GlfwClientApi_OpenGL) ? bd->Window : NULL; + vd->Window = glfwCreateWindow((int)viewport->Size.x, (int)viewport->Size.y, "No Title Yet", NULL, share_window); + vd->WindowOwned = true; + viewport->PlatformHandle = (void*)vd->Window; #ifdef _WIN32 - viewport->PlatformHandleRaw = glfwGetWin32Window(data->Window); + viewport->PlatformHandleRaw = glfwGetWin32Window(vd->Window); #endif - glfwSetWindowPos(data->Window, (int)viewport->Pos.x, (int)viewport->Pos.y); + glfwSetWindowPos(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y); // Install GLFW callbacks for secondary viewports - glfwSetMouseButtonCallback(data->Window, ImGui_ImplGlfw_MouseButtonCallback); - glfwSetScrollCallback(data->Window, ImGui_ImplGlfw_ScrollCallback); - glfwSetKeyCallback(data->Window, ImGui_ImplGlfw_KeyCallback); - glfwSetCharCallback(data->Window, ImGui_ImplGlfw_CharCallback); - glfwSetWindowCloseCallback(data->Window, ImGui_ImplGlfw_WindowCloseCallback); - glfwSetWindowPosCallback(data->Window, ImGui_ImplGlfw_WindowPosCallback); - glfwSetWindowSizeCallback(data->Window, ImGui_ImplGlfw_WindowSizeCallback); - if (g_ClientApi == GlfwClientApi_OpenGL) + glfwSetCursorEnterCallback(vd->Window, ImGui_ImplGlfw_CursorEnterCallback); + glfwSetMouseButtonCallback(vd->Window, ImGui_ImplGlfw_MouseButtonCallback); + glfwSetScrollCallback(vd->Window, ImGui_ImplGlfw_ScrollCallback); + glfwSetKeyCallback(vd->Window, ImGui_ImplGlfw_KeyCallback); + glfwSetCharCallback(vd->Window, ImGui_ImplGlfw_CharCallback); + glfwSetWindowCloseCallback(vd->Window, ImGui_ImplGlfw_WindowCloseCallback); + glfwSetWindowPosCallback(vd->Window, ImGui_ImplGlfw_WindowPosCallback); + glfwSetWindowSizeCallback(vd->Window, ImGui_ImplGlfw_WindowSizeCallback); + if (bd->ClientApi == GlfwClientApi_OpenGL) { - glfwMakeContextCurrent(data->Window); + glfwMakeContextCurrent(vd->Window); glfwSwapInterval(0); } } static void ImGui_ImplGlfw_DestroyWindow(ImGuiViewport* viewport) { - if (ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData) + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData) { - if (data->WindowOwned) + if (vd->WindowOwned) { #if !GLFW_HAS_MOUSE_PASSTHROUGH && GLFW_HAS_WINDOW_HOVERED && defined(_WIN32) HWND hwnd = (HWND)viewport->PlatformHandleRaw; ::RemovePropA(hwnd, "IMGUI_VIEWPORT"); #endif - glfwDestroyWindow(data->Window); + + // Release any keys that were pressed in the window being destroyed and are still held down, + // because we will not receive any release events after window is destroyed. + for (int i = 0; i < IM_ARRAYSIZE(bd->KeyOwnerWindows); i++) + if (bd->KeyOwnerWindows[i] == vd->Window) + ImGui_ImplGlfw_KeyCallback(vd->Window, i, 0, GLFW_RELEASE, 0); // Later params are only used for main viewport, on which this function is never called. + + glfwDestroyWindow(vd->Window); } - data->Window = NULL; - IM_DELETE(data); + vd->Window = NULL; + IM_DELETE(vd); } viewport->PlatformUserData = viewport->PlatformHandle = NULL; } @@ -637,7 +715,7 @@ static LRESULT CALLBACK WndProcNoInputs(HWND hWnd, UINT msg, WPARAM wParam, LPAR static void ImGui_ImplGlfw_ShowWindow(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; #if defined(_WIN32) // GLFW hack: Hide icon from task bar @@ -671,60 +749,60 @@ static void ImGui_ImplGlfw_ShowWindow(ImGuiViewport* viewport) #endif #endif - glfwShowWindow(data->Window); + glfwShowWindow(vd->Window); } static ImVec2 ImGui_ImplGlfw_GetWindowPos(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; int x = 0, y = 0; - glfwGetWindowPos(data->Window, &x, &y); + glfwGetWindowPos(vd->Window, &x, &y); return ImVec2((float)x, (float)y); } static void ImGui_ImplGlfw_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - data->IgnoreWindowPosEventFrame = ImGui::GetFrameCount(); - glfwSetWindowPos(data->Window, (int)pos.x, (int)pos.y); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount(); + glfwSetWindowPos(vd->Window, (int)pos.x, (int)pos.y); } static ImVec2 ImGui_ImplGlfw_GetWindowSize(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; int w = 0, h = 0; - glfwGetWindowSize(data->Window, &w, &h); + glfwGetWindowSize(vd->Window, &w, &h); return ImVec2((float)w, (float)h); } static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; #if __APPLE__ && !GLFW_HAS_OSX_WINDOW_POS_FIX // Native OS windows are positioned from the bottom-left corner on macOS, whereas on other platforms they are // positioned from the upper-left corner. GLFW makes an effort to convert macOS style coordinates, however it // doesn't handle it when changing size. We are manually moving the window in order for changes of size to be based // on the upper-left corner. int x, y, width, height; - glfwGetWindowPos(data->Window, &x, &y); - glfwGetWindowSize(data->Window, &width, &height); - glfwSetWindowPos(data->Window, x, y - height + size.y); + glfwGetWindowPos(vd->Window, &x, &y); + glfwGetWindowSize(vd->Window, &width, &height); + glfwSetWindowPos(vd->Window, x, y - height + size.y); #endif - data->IgnoreWindowSizeEventFrame = ImGui::GetFrameCount(); - glfwSetWindowSize(data->Window, (int)size.x, (int)size.y); + vd->IgnoreWindowSizeEventFrame = ImGui::GetFrameCount(); + glfwSetWindowSize(vd->Window, (int)size.x, (int)size.y); } static void ImGui_ImplGlfw_SetWindowTitle(ImGuiViewport* viewport, const char* title) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - glfwSetWindowTitle(data->Window, title); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + glfwSetWindowTitle(vd->Window, title); } static void ImGui_ImplGlfw_SetWindowFocus(ImGuiViewport* viewport) { #if GLFW_HAS_FOCUS_WINDOW - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - glfwFocusWindow(data->Window); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + glfwFocusWindow(vd->Window); #else // FIXME: What are the effect of not having this function? At the moment imgui doesn't actually call SetWindowFocus - we set that up ahead, will answer that question later. (void)viewport; @@ -733,38 +811,40 @@ static void ImGui_ImplGlfw_SetWindowFocus(ImGuiViewport* viewport) static bool ImGui_ImplGlfw_GetWindowFocus(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - return glfwGetWindowAttrib(data->Window, GLFW_FOCUSED) != 0; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + return glfwGetWindowAttrib(vd->Window, GLFW_FOCUSED) != 0; } static bool ImGui_ImplGlfw_GetWindowMinimized(ImGuiViewport* viewport) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - return glfwGetWindowAttrib(data->Window, GLFW_ICONIFIED) != 0; + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + return glfwGetWindowAttrib(vd->Window, GLFW_ICONIFIED) != 0; } #if GLFW_HAS_WINDOW_ALPHA static void ImGui_ImplGlfw_SetWindowAlpha(ImGuiViewport* viewport, float alpha) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - glfwSetWindowOpacity(data->Window, alpha); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + glfwSetWindowOpacity(vd->Window, alpha); } #endif static void ImGui_ImplGlfw_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - if (g_ClientApi == GlfwClientApi_OpenGL) - glfwMakeContextCurrent(data->Window); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + if (bd->ClientApi == GlfwClientApi_OpenGL) + glfwMakeContextCurrent(vd->Window); } static void ImGui_ImplGlfw_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - if (g_ClientApi == GlfwClientApi_OpenGL) + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + if (bd->ClientApi == GlfwClientApi_OpenGL) { - glfwMakeContextCurrent(data->Window); - glfwSwapBuffers(data->Window); + glfwMakeContextCurrent(vd->Window); + glfwSwapBuffers(vd->Window); } } @@ -773,7 +853,7 @@ static void ImGui_ImplGlfw_SwapBuffers(ImGuiViewport* viewport, void*) //-------------------------------------------------------------------------------------------------------- // We provide a Win32 implementation because this is such a common issue for IME users -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(__GNUC__) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) #define HAS_WIN32_IME 1 #include #ifdef _MSC_VER @@ -814,9 +894,10 @@ enum VkResult { VK_RESULT_MAX_ENUM = 0x7FFFFFFF }; extern "C" { extern GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); } static int ImGui_ImplGlfw_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_instance, const void* vk_allocator, ImU64* out_vk_surface) { - ImGuiViewportDataGlfw* data = (ImGuiViewportDataGlfw*)viewport->PlatformUserData; - IM_ASSERT(g_ClientApi == GlfwClientApi_Vulkan); - VkResult err = glfwCreateWindowSurface((VkInstance)vk_instance, data->Window, (const VkAllocationCallbacks*)vk_allocator, (VkSurfaceKHR*)out_vk_surface); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(bd->ClientApi == GlfwClientApi_Vulkan); + VkResult err = glfwCreateWindowSurface((VkInstance)vk_instance, vd->Window, (const VkAllocationCallbacks*)vk_allocator, (VkSurfaceKHR*)out_vk_surface); return (int)err; } #endif // GLFW_HAS_VULKAN @@ -824,6 +905,7 @@ static int ImGui_ImplGlfw_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_inst static void ImGui_ImplGlfw_InitPlatformInterface() { // Register platform interface (will be coupled with a renderer interface) + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Platform_CreateWindow = ImGui_ImplGlfw_CreateWindow; platform_io.Platform_DestroyWindow = ImGui_ImplGlfw_DestroyWindow; @@ -851,11 +933,11 @@ static void ImGui_ImplGlfw_InitPlatformInterface() // Register main window handle (which is owned by the main application, not by us) // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - ImGuiViewportDataGlfw* data = IM_NEW(ImGuiViewportDataGlfw)(); - data->Window = g_Window; - data->WindowOwned = false; - main_viewport->PlatformUserData = data; - main_viewport->PlatformHandle = (void*)g_Window; + ImGui_ImplGlfw_ViewportData* vd = IM_NEW(ImGui_ImplGlfw_ViewportData)(); + vd->Window = bd->Window; + vd->WindowOwned = false; + main_viewport->PlatformUserData = vd; + main_viewport->PlatformHandle = (void*)bd->Window; } static void ImGui_ImplGlfw_ShutdownPlatformInterface() diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_glfw.h b/examples/interactive/imgui-1.83/backends/imgui_impl_glfw.h similarity index 88% rename from examples/interactive/imgui-1.81/backends/imgui_impl_glfw.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_glfw.h index 18d7ce01..4fdb5e0b 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_glfw.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_glfw.h @@ -12,7 +12,8 @@ // Issues: // [ ] Platform: Multi-viewport support: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -35,6 +36,7 @@ IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); // GLFW callbacks // - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any. // - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call those function yourself from your own GLFW callbacks. +IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_glut.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_glut.cpp similarity index 96% rename from examples/interactive/imgui-1.81/backends/imgui_impl_glut.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_glut.cpp index 6be4682c..85bea776 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_glut.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_glut.cpp @@ -11,7 +11,8 @@ // [ ] Platform: Missing clipboard support (not supported by Glut). // [ ] Platform: Missing gamepad support. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_glut.h b/examples/interactive/imgui-1.83/backends/imgui_impl_glut.h similarity index 90% rename from examples/interactive/imgui-1.81/backends/imgui_impl_glut.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_glut.h index 2f18ab76..96c779fd 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_glut.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_glut.h @@ -11,7 +11,8 @@ // [ ] Platform: Missing clipboard support (not supported by Glut). // [ ] Platform: Missing gamepad support. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.cpp similarity index 95% rename from examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.cpp index c4f83a80..aa6b3206 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.cpp @@ -6,12 +6,14 @@ // Missing features: // [ ] Renderer: Clipping rectangles are not honored. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-05-19: Renderer: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) // 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. // 2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter(). // 2018-11-30: Misc: Setting up io.BackendPlatformName/io.BackendRendererName so they can be displayed in the About Window. @@ -87,7 +89,7 @@ void ImGui_Marmalade_RenderDrawData(ImDrawData* draw_data) pCurrentMaterial->SetAlphaMode(CIwMaterial::ALPHA_BLEND); pCurrentMaterial->SetDepthWriteMode(CIwMaterial::DEPTH_WRITE_NORMAL); pCurrentMaterial->SetAlphaTestMode(CIwMaterial::ALPHATEST_DISABLED); - pCurrentMaterial->SetTexture((CIwTexture*)pcmd->TextureId); + pCurrentMaterial->SetTexture((CIwTexture*)pcmd->GetTexID()); IwGxSetMaterial(pCurrentMaterial); IwGxDrawPrims(IW_GX_TRI_LIST, (uint16*)idx_buffer, pcmd->ElemCount); } @@ -220,7 +222,8 @@ bool ImGui_Marmalade_Init(bool install_callbacks) ImGuiIO& io = ImGui::GetIO(); io.BackendPlatformName = io.BackendRendererName = "imgui_impl_marmalade"; - io.KeyMap[ImGuiKey_Tab] = s3eKeyTab; // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = s3eKeyTab io.KeyMap[ImGuiKey_LeftArrow] = s3eKeyLeft; io.KeyMap[ImGuiKey_RightArrow] = s3eKeyRight; io.KeyMap[ImGuiKey_UpArrow] = s3eKeyUp; diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.h b/examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.h similarity index 84% rename from examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.h index db521d58..87aaa478 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_marmalade.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_marmalade.h @@ -4,7 +4,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'CIwTexture*' as ImTextureID. Read the FAQ about ImTextureID! -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_metal.h b/examples/interactive/imgui-1.83/backends/imgui_impl_metal.h similarity index 85% rename from examples/interactive/imgui-1.81/backends/imgui_impl_metal.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_metal.h index a6f40006..147b24a8 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_metal.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_metal.h @@ -7,7 +7,8 @@ // Missing features: // [ ] Renderer: Multi-viewport / platform windows. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_metal.mm b/examples/interactive/imgui-1.83/backends/imgui_impl_metal.mm similarity index 97% rename from examples/interactive/imgui-1.81/backends/imgui_impl_metal.mm rename to examples/interactive/imgui-1.83/backends/imgui_impl_metal.mm index cab733da..5180f6f7 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_metal.mm +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_metal.mm @@ -7,12 +7,15 @@ // Missing features: // [ ] Renderer: Multi-viewport / platform windows. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. // 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. // 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. @@ -388,10 +391,10 @@ - (void)enqueueReusableBuffer:(MetalBuffer *)buffer pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; - pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; - pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; @@ -523,8 +526,8 @@ - (void)renderDrawData:(ImDrawData *)drawData // Bind texture, Draw - if (pcmd->TextureId != NULL) - [commandEncoder setFragmentTexture:(__bridge id)(pcmd->TextureId) atIndex:0]; + if (ImTextureID tex_id = pcmd->GetTexID()) + [commandEncoder setFragmentTexture:(__bridge id)(tex_id) atIndex:0]; [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.cpp similarity index 85% rename from examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.cpp index d8a49ea5..e5b9d78c 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.cpp @@ -5,7 +5,8 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -20,6 +21,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) // 2021-01-03: OpenGL: Backup, setup and restore GL_SHADE_MODEL state, disable GL_STENCIL_TEST and disable GL_NORMAL_ARRAY client state to increase compatibility with legacy OpenGL applications. // 2020-01-23: OpenGL: Backup, setup and restore GL_TEXTURE_ENV to increase compatibility with legacy OpenGL applications. // 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. @@ -55,8 +58,19 @@ #include #endif -// OpenGL Data -static GLuint g_FontTexture = 0; +struct ImGui_ImplOpenGL2_Data +{ + GLuint FontTexture; + + ImGui_ImplOpenGL2_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL2_Data* ImGui_ImplOpenGL2_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL2_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} // Forward Declarations static void ImGui_ImplOpenGL2_InitPlatformInterface(); @@ -65,25 +79,39 @@ static void ImGui_ImplOpenGL2_ShutdownPlatformInterface(); // Functions bool ImGui_ImplOpenGL2_Init() { - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_opengl2"; io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplOpenGL2_InitPlatformInterface(); + return true; } void ImGui_ImplOpenGL2_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + ImGui_ImplOpenGL2_ShutdownPlatformInterface(); ImGui_ImplOpenGL2_DestroyDeviceObjects(); + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplOpenGL2_NewFrame() { - if (!g_FontTexture) + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplOpenGL2_Init()?"); + + if (!bd->FontTexture) ImGui_ImplOpenGL2_CreateDeviceObjects(); } @@ -92,6 +120,7 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // In order to composite our output buffer we need to preserve alpha glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); @@ -194,7 +223,7 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); // Bind texture, Draw - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); } } @@ -223,6 +252,7 @@ bool ImGui_ImplOpenGL2_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. @@ -230,15 +260,15 @@ bool ImGui_ImplOpenGL2_CreateFontsTexture() // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGenTextures(1, &g_FontTexture); - glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glGenTextures(1, &bd->FontTexture); + glBindTexture(GL_TEXTURE_2D, bd->FontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)g_FontTexture); + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); @@ -248,12 +278,13 @@ bool ImGui_ImplOpenGL2_CreateFontsTexture() void ImGui_ImplOpenGL2_DestroyFontsTexture() { - if (g_FontTexture) + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + if (bd->FontTexture) { - ImGuiIO& io = ImGui::GetIO(); - glDeleteTextures(1, &g_FontTexture); + glDeleteTextures(1, &bd->FontTexture); io.Fonts->SetTexID(0); - g_FontTexture = 0; + bd->FontTexture = 0; } } diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.h b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.h similarity index 87% rename from examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.h index ebc055d6..780204a2 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl2.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl2.h @@ -5,7 +5,8 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.cpp similarity index 76% rename from examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.cpp index f0d54676..bc34c7ea 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.cpp @@ -8,13 +8,21 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state. +// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader. +// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version. +// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater. +// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer. // 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state. // 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state. // 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x) @@ -90,6 +98,12 @@ // GL includes #if defined(IMGUI_IMPL_OPENGL_ES2) #include +#if defined(__EMSCRIPTEN__) +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include +#endif #elif defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) #include // Use GL ES 3 @@ -128,6 +142,17 @@ using namespace gl; #endif #endif +// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension +#ifndef IMGUI_IMPL_OPENGL_ES2 +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#define glBindVertexArray glBindVertexArrayOES +#define glGenVertexArrays glGenVertexArraysOES +#define glDeleteVertexArrays glDeleteVertexArraysOES +#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES +#endif + // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET @@ -143,14 +168,35 @@ using namespace gl; #define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART #endif +// Desktop GL use extension detection +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS +#endif + // OpenGL Data -static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) -static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. -static GLuint g_FontTexture = 0; -static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; -static GLint g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location -static GLuint g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location -static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; +struct ImGui_ImplOpenGL3_Data +{ + GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) + char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings. + GLuint FontTexture; + GLuint ShaderHandle; + GLint AttribLocationTex; // Uniforms location + GLint AttribLocationProjMtx; + GLuint AttribLocationVtxPos; // Vertex attributes location + GLuint AttribLocationVtxUV; + GLuint AttribLocationVtxColor; + unsigned int VboHandle, ElementsHandle; + bool HasClipOrigin; + + ImGui_ImplOpenGL3_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} // Forward Declarations static void ImGui_ImplOpenGL3_InitPlatformInterface(); @@ -159,6 +205,14 @@ static void ImGui_ImplOpenGL3_ShutdownPlatformInterface(); // Functions bool ImGui_ImplOpenGL3_Init(const char* glsl_version) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();; + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl3"; + // Query for GL version (e.g. 320 for GL 3.2) #if !defined(IMGUI_IMPL_OPENGL_ES2) GLint major = 0; @@ -171,38 +225,34 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) const char* gl_version = (const char*)glGetString(GL_VERSION); sscanf(gl_version, "%d.%d", &major, &minor); } - g_GlVersion = (GLuint)(major * 100 + minor * 10); + bd->GlVersion = (GLuint)(major * 100 + minor * 10); #else - g_GlVersion = 200; // GLES 2 + bd->GlVersion = 200; // GLES 2 #endif - // Setup backend capabilities flags - ImGuiIO& io = ImGui::GetIO(); - io.BackendRendererName = "imgui_impl_opengl3"; #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET - if (g_GlVersion >= 320) + if (bd->GlVersion >= 320) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) // Store GLSL version string so we can refer to it later in case we recreate shaders. // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. -#if defined(IMGUI_IMPL_OPENGL_ES2) if (glsl_version == NULL) + { +#if defined(IMGUI_IMPL_OPENGL_ES2) glsl_version = "#version 100"; #elif defined(IMGUI_IMPL_OPENGL_ES3) - if (glsl_version == NULL) glsl_version = "#version 300 es"; #elif defined(__APPLE__) - if (glsl_version == NULL) glsl_version = "#version 150"; #else - if (glsl_version == NULL) glsl_version = "#version 130"; #endif - IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); - strcpy(g_GlslVersionString, glsl_version); - strcat(g_GlslVersionString, "\n"); + } + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString)); + strcpy(bd->GlslVersionString, glsl_version); + strcat(bd->GlslVersionString, "\n"); // Debugging construct to make it easily visible in the IDE and debugger which GL loader has been selected. // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! @@ -235,6 +285,19 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) GLint current_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + // Detect extensions we support + bd->HasClipOrigin = (bd->GlVersion >= 450); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS + GLint num_extensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + for (GLint i = 0; i < num_extensions; i++) + { + const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); + if (extension != NULL && strcmp(extension, "GL_ARB_clip_control") == 0) + bd->HasClipOrigin = true; + } +#endif + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplOpenGL3_InitPlatformInterface(); @@ -243,28 +306,39 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) void ImGui_ImplOpenGL3_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + ImGui_ImplOpenGL3_ShutdownPlatformInterface(); ImGui_ImplOpenGL3_DestroyDeviceObjects(); + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplOpenGL3_NewFrame() { - if (!g_ShaderHandle) + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplOpenGL3_Init()?"); + + if (!bd->ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); glEnable(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - if (g_GlVersion >= 310) + if (bd->GlVersion >= 310) glDisable(GL_PRIMITIVE_RESTART); #endif #ifdef GL_POLYGON_MODE @@ -272,11 +346,14 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid #endif // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) -#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) +#if defined(GL_CLIP_ORIGIN) bool clip_origin_lower_left = true; - GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); - if (current_clip_origin == GL_UPPER_LEFT) - clip_origin_lower_left = false; + if (bd->HasClipOrigin) + { + GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); + if (current_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; + } #endif // Setup viewport, orthographic projection matrix @@ -286,7 +363,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; -#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) +#if defined(GL_CLIP_ORIGIN) if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left #endif const float ortho_projection[4][4] = @@ -296,29 +373,29 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid { 0.0f, 0.0f, -1.0f, 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, }; - glUseProgram(g_ShaderHandle); - glUniform1i(g_AttribLocationTex, 0); - glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + glUseProgram(bd->ShaderHandle); + glUniform1i(bd->AttribLocationTex, 0); + glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - if (g_GlVersion >= 330) + if (bd->GlVersion >= 330) glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif (void)vertex_array_object; -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY glBindVertexArray(vertex_array_object); #endif // Bind vertex/index buffers and setup attributes for ImDrawVert - glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); - glEnableVertexAttribArray(g_AttribLocationVtxPos); - glEnableVertexAttribArray(g_AttribLocationVtxUV); - glEnableVertexAttribArray(g_AttribLocationVtxColor); - glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); - glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); - glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle); + glEnableVertexAttribArray(bd->AttribLocationVtxPos); + glEnableVertexAttribArray(bd->AttribLocationVtxUV); + glEnableVertexAttribArray(bd->AttribLocationVtxColor); + glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); } // OpenGL3 Render function. @@ -332,16 +409,18 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (fb_width <= 0 || fb_height <= 0) return; + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + // Backup GL state GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - GLuint last_sampler; if (g_GlVersion >= 330) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; } + GLuint last_sampler; if (bd->GlVersion >= 330) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; } #endif GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object); #endif #ifdef GL_POLYGON_MODE @@ -361,14 +440,14 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - GLboolean last_enable_primitive_restart = (g_GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; + GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; #endif // Setup desired GL state // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. GLuint vertex_array_object = 0; -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY glGenVertexArrays(1, &vertex_array_object); #endif ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); @@ -413,9 +492,9 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); // Bind texture, Draw - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET - if (g_GlVersion >= 320) + if (bd->GlVersion >= 320) glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); else #endif @@ -426,7 +505,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) } // Destroy the temporary VAO -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY glDeleteVertexArrays(1, &vertex_array_object); #endif @@ -434,11 +513,11 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - if (g_GlVersion >= 330) + if (bd->GlVersion >= 330) glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY glBindVertexArray(last_vertex_array_object); #endif glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); @@ -450,7 +529,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART - if (g_GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } + if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } #endif #ifdef GL_POLYGON_MODE @@ -462,8 +541,10 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) bool ImGui_ImplOpenGL3_CreateFontsTexture() { - // Build texture atlas ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Build texture atlas unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. @@ -471,8 +552,8 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture() // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGenTextures(1, &g_FontTexture); - glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glGenTextures(1, &bd->FontTexture); + glBindTexture(GL_TEXTURE_2D, bd->FontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); #ifdef GL_UNPACK_ROW_LENGTH @@ -481,7 +562,7 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)g_FontTexture); + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); @@ -491,23 +572,25 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture() void ImGui_ImplOpenGL3_DestroyFontsTexture() { - if (g_FontTexture) + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->FontTexture) { - ImGuiIO& io = ImGui::GetIO(); - glDeleteTextures(1, &g_FontTexture); + glDeleteTextures(1, &bd->FontTexture); io.Fonts->SetTexID(0); - g_FontTexture = 0; + bd->FontTexture = 0; } } // 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. static bool CheckShader(GLuint handle, const char* desc) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); GLint status = 0, log_length = 0; glGetShaderiv(handle, GL_COMPILE_STATUS, &status); glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) - fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString); if (log_length > 1) { ImVector buf; @@ -521,11 +604,12 @@ static bool CheckShader(GLuint handle, const char* desc) // If you get an error please report on GitHub. You may try different GL context version or GLSL version. static bool CheckProgram(GLuint handle, const char* desc) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); GLint status = 0, log_length = 0; glGetProgramiv(handle, GL_LINK_STATUS, &status); glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) - fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString); if (log_length > 1) { ImVector buf; @@ -538,18 +622,20 @@ static bool CheckProgram(GLuint handle, const char* desc) bool ImGui_ImplOpenGL3_CreateDeviceObjects() { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + // Backup GL state GLint last_texture, last_array_buffer; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); #endif // Parse GLSL version string int glsl_version = 130; - sscanf(g_GlslVersionString, "#version %d", &glsl_version); + sscanf(bd->GlslVersionString, "#version %d", &glsl_version); const GLchar* vertex_shader_glsl_120 = "uniform mat4 ProjMtx;\n" @@ -676,40 +762,46 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() } // Create shaders - const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; - g_VertHandle = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); - glCompileShader(g_VertHandle); - CheckShader(g_VertHandle, "vertex shader"); - - const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; - g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); - glCompileShader(g_FragHandle); - CheckShader(g_FragHandle, "fragment shader"); - - g_ShaderHandle = glCreateProgram(); - glAttachShader(g_ShaderHandle, g_VertHandle); - glAttachShader(g_ShaderHandle, g_FragHandle); - glLinkProgram(g_ShaderHandle); - CheckProgram(g_ShaderHandle, "shader program"); - - g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); - g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); - g_AttribLocationVtxPos = (GLuint)glGetAttribLocation(g_ShaderHandle, "Position"); - g_AttribLocationVtxUV = (GLuint)glGetAttribLocation(g_ShaderHandle, "UV"); - g_AttribLocationVtxColor = (GLuint)glGetAttribLocation(g_ShaderHandle, "Color"); + const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader }; + GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vert_handle, 2, vertex_shader_with_version, NULL); + glCompileShader(vert_handle); + CheckShader(vert_handle, "vertex shader"); + + const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader }; + GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(frag_handle, 2, fragment_shader_with_version, NULL); + glCompileShader(frag_handle); + CheckShader(frag_handle, "fragment shader"); + + // Link + bd->ShaderHandle = glCreateProgram(); + glAttachShader(bd->ShaderHandle, vert_handle); + glAttachShader(bd->ShaderHandle, frag_handle); + glLinkProgram(bd->ShaderHandle); + CheckProgram(bd->ShaderHandle, "shader program"); + + glDetachShader(bd->ShaderHandle, vert_handle); + glDetachShader(bd->ShaderHandle, frag_handle); + glDeleteShader(vert_handle); + glDeleteShader(frag_handle); + + bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture"); + bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx"); + bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position"); + bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV"); + bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color"); // Create buffers - glGenBuffers(1, &g_VboHandle); - glGenBuffers(1, &g_ElementsHandle); + glGenBuffers(1, &bd->VboHandle); + glGenBuffers(1, &bd->ElementsHandle); ImGui_ImplOpenGL3_CreateFontsTexture(); // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); -#ifndef IMGUI_IMPL_OPENGL_ES2 +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY glBindVertexArray(last_vertex_array); #endif @@ -718,14 +810,10 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() void ImGui_ImplOpenGL3_DestroyDeviceObjects() { - if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } - if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } - if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } - if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } - if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } - if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } - if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } - + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } + if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } + if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } ImGui_ImplOpenGL3_DestroyFontsTexture(); } diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.h b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.h similarity index 94% rename from examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.h index 82860eb1..283dc8cf 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_opengl3.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_opengl3.h @@ -8,7 +8,8 @@ // [X] Renderer: Multi-viewport support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_osx.h b/examples/interactive/imgui-1.83/backends/imgui_impl_osx.h similarity index 82% rename from examples/interactive/imgui-1.81/backends/imgui_impl_osx.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_osx.h index fe5566d8..a17bbb4a 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_osx.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_osx.h @@ -9,7 +9,8 @@ // [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. // [ ] Platform: Multi-viewport / platform windows. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_osx.mm b/examples/interactive/imgui-1.83/backends/imgui_impl_osx.mm similarity index 89% rename from examples/interactive/imgui-1.81/backends/imgui_impl_osx.mm rename to examples/interactive/imgui-1.83/backends/imgui_impl_osx.mm index c81abd39..0b7f8bff 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_osx.mm +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_osx.mm @@ -9,7 +9,8 @@ // [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. // [ ] Platform: Multi-viewport / platform windows. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -19,6 +20,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key. +// 2021-04-19: Inputs: Added a fix for keys remaining stuck in pressed state when CMD-tabbing into different application. // 2021-01-27: Inputs: Added a fix for mouse position not being reported when mouse buttons other than left one are down. // 2020-10-28: Inputs: Added a fix for handling keypad-enter key. // 2020-05-25: Inputs: Added a fix for missing trackpad clicks when done with "soft tap". @@ -31,12 +34,15 @@ // 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. // 2018-07-07: Initial version. +@class ImFocusObserver; + // Data static CFAbsoluteTime g_Time = 0.0; static NSCursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; static bool g_MouseCursorHidden = false; static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {}; static bool g_MouseDown[ImGuiMouseButton_COUNT] = {}; +static ImFocusObserver* g_FocusObserver = NULL; // Undocumented methods for creating cursors. @interface NSCursor() @@ -46,6 +52,31 @@ + (id)_windowResizeNorthSouthCursor; + (id)_windowResizeEastWestCursor; @end +static void resetKeys() +{ + ImGuiIO& io = ImGui::GetIO(); + memset(io.KeysDown, 0, sizeof(io.KeysDown)); + io.KeyCtrl = io.KeyShift = io.KeyAlt = io.KeySuper = false; +} + +@interface ImFocusObserver : NSObject + +- (void)onApplicationBecomeInactive:(NSNotification*)aNotification; + +@end + +@implementation ImFocusObserver + +- (void)onApplicationBecomeInactive:(NSNotification*)aNotification +{ + // Unfocused applications do not receive input events, therefore we must manually + // release any pressed keys when application loses focus, otherwise they would remain + // stuck in a pressed state. https://github.com/ocornut/imgui/issues/3832 + resetKeys(); +} + +@end + // Functions bool ImGui_ImplOSX_Init() { @@ -124,11 +155,18 @@ bool ImGui_ImplOSX_Init() return s_clipboard.Data; }; + g_FocusObserver = [[ImFocusObserver alloc] init]; + [[NSNotificationCenter defaultCenter] addObserver:g_FocusObserver + selector:@selector(onApplicationBecomeInactive:) + name:NSApplicationDidResignActiveNotification + object:nil]; + return true; } void ImGui_ImplOSX_Shutdown() { + g_FocusObserver = NULL; } static void ImGui_ImplOSX_UpdateMouseCursorAndButtons() @@ -201,13 +239,6 @@ static int mapCharacterToKey(int c) return -1; } -static void resetKeys() -{ - ImGuiIO& io = ImGui::GetIO(); - for (int n = 0; n < IM_ARRAYSIZE(io.KeysDown); n++) - io.KeysDown[n] = false; -} - bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) { ImGuiIO& io = ImGui::GetIO(); @@ -274,12 +305,12 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) for (NSUInteger i = 0; i < len; i++) { int c = [str characterAtIndex:i]; - if (!io.KeyCtrl && !(c >= 0xF700 && c <= 0xFFFF) && c != 127) + if (!io.KeySuper && !(c >= 0xF700 && c <= 0xFFFF) && c != 127) io.AddInputCharacter((unsigned int)c); // We must reset in case we're pressing a sequence of special keys while keeping the command pressed int key = mapCharacterToKey(c); - if (key != -1 && key < 256 && !io.KeyCtrl) + if (key != -1 && key < 256 && !io.KeySuper) resetKeys(); if (key != -1) io.KeysDown[key] = true; diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_sdl.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_sdl.cpp similarity index 64% rename from examples/interactive/imgui-1.81/backends/imgui_impl_sdl.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_sdl.cpp index 4f2787ec..aa2acbd1 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_sdl.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_sdl.cpp @@ -1,7 +1,7 @@ // dear imgui: Platform Backend for SDL2 // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) // (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) -// (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.) +// (Prefer SDL 2.0.5+ for full feature support.) // Implemented features: // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. @@ -13,13 +13,18 @@ // [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. // [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+) +// 2021-06:29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950) // 2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends. // 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2). // 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state). @@ -58,37 +63,53 @@ #include "TargetConditionals.h" #endif -#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION_ATLEAST(2,0,4) +#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE (SDL_VERSION_ATLEAST(2,0,4) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS)) +#define SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH SDL_VERSION_ATLEAST(2,0,5) #define SDL_HAS_WINDOW_ALPHA SDL_VERSION_ATLEAST(2,0,5) #define SDL_HAS_ALWAYS_ON_TOP SDL_VERSION_ATLEAST(2,0,5) #define SDL_HAS_USABLE_DISPLAY_BOUNDS SDL_VERSION_ATLEAST(2,0,5) #define SDL_HAS_PER_MONITOR_DPI SDL_VERSION_ATLEAST(2,0,4) #define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) -#define SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH SDL_VERSION_ATLEAST(2,0,5) #if !SDL_HAS_VULKAN static const Uint32 SDL_WINDOW_VULKAN = 0x10000000; #endif -// Data -static SDL_Window* g_Window = NULL; -static Uint64 g_Time = 0; -static bool g_MousePressed[3] = { false, false, false }; -static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; -static char* g_ClipboardTextData = NULL; -static bool g_MouseCanUseGlobalState = true; -static bool g_UseVulkan = false; +// SDL Data +struct ImGui_ImplSDL2_Data +{ + SDL_Window* Window; + Uint64 Time; + bool MousePressed[3]; + SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT]; + char* ClipboardTextData; + bool MouseCanUseGlobalState; + bool UseVulkan; + + ImGui_ImplSDL2_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in this backend. +// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. +static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; +} // Forward Declarations static void ImGui_ImplSDL2_UpdateMonitors(); static void ImGui_ImplSDL2_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context); static void ImGui_ImplSDL2_ShutdownPlatformInterface(); +// Functions static const char* ImGui_ImplSDL2_GetClipboardText(void*) { - if (g_ClipboardTextData) - SDL_free(g_ClipboardTextData); - g_ClipboardTextData = SDL_GetClipboardText(); - return g_ClipboardTextData; + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + if (bd->ClipboardTextData) + SDL_free(bd->ClipboardTextData); + bd->ClipboardTextData = SDL_GetClipboardText(); + return bd->ClipboardTextData; } static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) @@ -104,6 +125,8 @@ static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) { ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + switch (event->type) { case SDL_MOUSEWHEEL: @@ -116,9 +139,9 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) } case SDL_MOUSEBUTTONDOWN: { - if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true; - if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true; - if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true; + if (event->button.button == SDL_BUTTON_LEFT) { bd->MousePressed[0] = true; } + if (event->button.button == SDL_BUTTON_RIGHT) { bd->MousePressed[1] = true; } + if (event->button.button == SDL_BUTTON_MIDDLE) { bd->MousePressed[2] = true; } return true; } case SDL_TEXTINPUT: @@ -163,17 +186,33 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) static bool ImGui_ImplSDL2_Init(SDL_Window* window, void* sdl_gl_context) { - g_Window = window; - - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); - io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); + + // Check and store if we are on a SDL backend that supports global mouse position + // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) + const char* sdl_backend = SDL_GetCurrentVideoDriver(); + const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; + bool mouse_can_use_global_state = false; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE - io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) + for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++) + if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0) + mouse_can_use_global_state = true; #endif + + // Setup backend capabilities flags + ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); + io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = "imgui_impl_sdl"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + if (mouse_can_use_global_state) + io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + bd->Window = window; + bd->MouseCanUseGlobalState = mouse_can_use_global_state; + + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB; io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; @@ -202,29 +241,35 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, void* sdl_gl_context) io.ClipboardUserData = NULL; // Load mouse cursors - g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); - g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); - g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); - g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); - g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); - g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); - g_MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); - - // Check and store if we are on Wayland - g_MouseCanUseGlobalState = strncmp(SDL_GetCurrentVideoDriver(), "wayland", 7) != 0; + bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); // Our mouse update function expect PlatformHandle to be filled for the main viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); main_viewport->PlatformHandle = (void*)window; -#if defined(_WIN32) +#ifdef _WIN32 SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWindowWMInfo(window, &info)) main_viewport->PlatformHandleRaw = info.info.win.window; #endif + // Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event. + // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered. + // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application. + // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click: + // you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) +#if SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + // Update monitors ImGui_ImplSDL2_UpdateMonitors(); @@ -238,7 +283,6 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, void* sdl_gl_context) bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) { - (void)sdl_gl_context; // Viewport branch will need this. return ImGui_ImplSDL2_Init(window, sdl_gl_context); } @@ -247,8 +291,11 @@ bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) #if !SDL_HAS_VULKAN IM_ASSERT(0 && "Unsupported"); #endif - g_UseVulkan = true; - return ImGui_ImplSDL2_Init(window, NULL); + if (!ImGui_ImplSDL2_Init(window, NULL)) + return false; + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + bd->UseVulkan = true; + return true; } bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) @@ -266,93 +313,93 @@ bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window) void ImGui_ImplSDL2_Shutdown() { - ImGui_ImplSDL2_ShutdownPlatformInterface(); - g_Window = NULL; + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); - // Destroy last known clipboard data - if (g_ClipboardTextData) - SDL_free(g_ClipboardTextData); - g_ClipboardTextData = NULL; + ImGui_ImplSDL2_ShutdownPlatformInterface(); - // Destroy SDL mouse cursors + if (bd->ClipboardTextData) + SDL_free(bd->ClipboardTextData); for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) - SDL_FreeCursor(g_MouseCursors[cursor_n]); - memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); + SDL_FreeCursor(bd->MouseCursors[cursor_n]); + + io.BackendPlatformName = NULL; + io.BackendPlatformUserData = NULL; + IM_DELETE(bd); } // This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4. static void ImGui_ImplSDL2_UpdateMousePosAndButtons() { + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); + + ImVec2 mouse_pos_prev = io.MousePos; + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); io.MouseHoveredViewport = 0; - // [1] - // Only when requested by io.WantSetMousePos: set OS mouse pos from Dear ImGui mouse pos. - // (rarely used, mostly when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + // Update mouse buttons + int mouse_x_local, mouse_y_local; + Uint32 mouse_buttons = SDL_GetMouseState(&mouse_x_local, &mouse_y_local); + io.MouseDown[0] = bd->MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[1] = bd->MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; + io.MouseDown[2] = bd->MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; + bd->MousePressed[0] = bd->MousePressed[1] = bd->MousePressed[2] = false; + + // Obtain focused and hovered window. We forward mouse input when focused or when hovered (and no other window is capturing) +#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + SDL_Window* hovered_window = SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH ? SDL_GetMouseFocus() : NULL; // This is better but is only reliably useful with SDL 2.0.5+ and SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH. + SDL_Window* mouse_window = NULL; + if (hovered_window && (bd->Window == hovered_window || ImGui::FindViewportByPlatformHandle((void*)hovered_window))) + mouse_window = hovered_window; + else if (focused_window && (bd->Window == focused_window || ImGui::FindViewportByPlatformHandle((void*)focused_window))) + mouse_window = focused_window; + + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside + SDL_CaptureMouse(ImGui::IsAnyMouseDown() ? SDL_TRUE : SDL_FALSE); +#else + // SDL 2.0.3 and non-windowed systems: single-viewport only + SDL_Window* mouse_window = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) ? bd->Window : NULL; +#endif + + if (mouse_window == NULL) + return; + + // Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) if (io.WantSetMousePos) { #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - SDL_WarpMouseGlobal((int)io.MousePos.x, (int)io.MousePos.y); + SDL_WarpMouseGlobal((int)mouse_pos_prev.x, (int)mouse_pos_prev.y); else #endif - SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y); + SDL_WarpMouseInWindow(bd->Window, (int)mouse_pos_prev.x, (int)mouse_pos_prev.y); } - else - { - io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); - } - - // [2] - // Set Dear ImGui mouse pos from OS mouse pos + get buttons. (this is the common behavior) - int mouse_x_local, mouse_y_local; - Uint32 mouse_buttons = SDL_GetMouseState(&mouse_x_local, &mouse_y_local); - io.MouseDown[0] = g_MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. - io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; - io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; - g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false; - -#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) - if (g_MouseCanUseGlobalState) + // Set Dear ImGui mouse position from OS position + get buttons. (this is the common behavior) + if (bd->MouseCanUseGlobalState) { - // SDL 2.0.4 and later has SDL_GetGlobalMouseState() and SDL_CaptureMouse() int mouse_x_global, mouse_y_global; SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) - if (SDL_Window* focused_window = SDL_GetKeyboardFocus()) - if (ImGui::FindViewportByPlatformHandle((void*)focused_window) != NULL) - io.MousePos = ImVec2((float)mouse_x_global, (float)mouse_y_global); + io.MousePos = ImVec2((float)mouse_x_global, (float)mouse_y_global); } else { - // Single-viewport mode: mouse position in client window coordinatesio.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) - if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS) - { - int window_x, window_y; - SDL_GetWindowPosition(g_Window, &window_x, &window_y); - io.MousePos = ImVec2((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y)); - } + // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) + // Unlike local position obtained earlier this will be valid when straying out of bounds. + int window_x, window_y; + SDL_GetWindowPosition(mouse_window, &window_x, &window_y); + io.MousePos = ImVec2((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y)); } } else { - if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS) - io.MousePos = ImVec2((float)mouse_x_local, (float)mouse_y_local); - } - - // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger the OS window resize cursor. - // The function is only supported from SDL 2.0.4 (released Jan 2016) - bool any_mouse_button_down = ImGui::IsAnyMouseDown(); - SDL_CaptureMouse(any_mouse_button_down ? SDL_TRUE : SDL_FALSE); -#else - // SDL 2.0.3 and before: single-viewport only - if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS) io.MousePos = ImVec2((float)mouse_x_local, (float)mouse_y_local); -#endif + } } static void ImGui_ImplSDL2_UpdateMouseCursor() @@ -360,6 +407,7 @@ static void ImGui_ImplSDL2_UpdateMouseCursor() ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) @@ -370,7 +418,7 @@ static void ImGui_ImplSDL2_UpdateMouseCursor() else { // Show OS mouse cursor - SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + SDL_SetCursor(bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]); SDL_ShowCursor(SDL_TRUE); } } @@ -444,18 +492,19 @@ static void ImGui_ImplSDL2_UpdateMonitors() } } -void ImGui_ImplSDL2_NewFrame(SDL_Window* window) +void ImGui_ImplSDL2_NewFrame() { + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDL2_Init()?"); ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); // Setup display size (every frame to accommodate for window resizing) int w, h; int display_w, display_h; - SDL_GetWindowSize(window, &w, &h); - if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + SDL_GetWindowSize(bd->Window, &w, &h); + if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) w = h = 0; - SDL_GL_GetDrawableSize(window, &display_w, &display_h); + SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h); io.DisplaySize = ImVec2((float)w, (float)h); if (w > 0 && h > 0) io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); @@ -463,8 +512,8 @@ void ImGui_ImplSDL2_NewFrame(SDL_Window* window) // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) static Uint64 frequency = SDL_GetPerformanceFrequency(); Uint64 current_time = SDL_GetPerformanceCounter(); - io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f); - g_Time = current_time; + io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f); + bd->Time = current_time; ImGui_ImplSDL2_UpdateMousePosAndButtons(); ImGui_ImplSDL2_UpdateMouseCursor(); @@ -480,24 +529,25 @@ void ImGui_ImplSDL2_NewFrame(SDL_Window* window) //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataSDL2 +struct ImGui_ImplSDL2_ViewportData { SDL_Window* Window; Uint32 WindowID; bool WindowOwned; SDL_GLContext GLContext; - ImGuiViewportDataSDL2() { Window = NULL; WindowID = 0; WindowOwned = false; GLContext = NULL; } - ~ImGuiViewportDataSDL2() { IM_ASSERT(Window == NULL && GLContext == NULL); } + ImGui_ImplSDL2_ViewportData() { Window = NULL; WindowID = 0; WindowOwned = false; GLContext = NULL; } + ~ImGui_ImplSDL2_ViewportData() { IM_ASSERT(Window == NULL && GLContext == NULL); } }; static void ImGui_ImplSDL2_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = IM_NEW(ImGuiViewportDataSDL2)(); - viewport->PlatformUserData = data; + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + ImGui_ImplSDL2_ViewportData* vd = IM_NEW(ImGui_ImplSDL2_ViewportData)(); + viewport->PlatformUserData = vd; ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - ImGuiViewportDataSDL2* main_viewport_data = (ImGuiViewportDataSDL2*)main_viewport->PlatformUserData; + ImGui_ImplSDL2_ViewportData* main_viewport_data = (ImGui_ImplSDL2_ViewportData*)main_viewport->PlatformUserData; // Share GL resources with main context bool use_opengl = (main_viewport_data->GLContext != NULL); @@ -510,8 +560,8 @@ static void ImGui_ImplSDL2_CreateWindow(ImGuiViewport* viewport) } Uint32 sdl_flags = 0; - sdl_flags |= use_opengl ? SDL_WINDOW_OPENGL : (g_UseVulkan ? SDL_WINDOW_VULKAN : 0); - sdl_flags |= SDL_GetWindowFlags(g_Window) & SDL_WINDOW_ALLOW_HIGHDPI; + sdl_flags |= use_opengl ? SDL_WINDOW_OPENGL : (bd->UseVulkan ? SDL_WINDOW_VULKAN : 0); + sdl_flags |= SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_ALLOW_HIGHDPI; sdl_flags |= SDL_WINDOW_HIDDEN; sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? SDL_WINDOW_BORDERLESS : 0; sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? 0 : SDL_WINDOW_RESIZABLE; @@ -522,43 +572,43 @@ static void ImGui_ImplSDL2_CreateWindow(ImGuiViewport* viewport) #if SDL_HAS_ALWAYS_ON_TOP sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0; #endif - data->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Pos.x, (int)viewport->Pos.y, (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags); - data->WindowOwned = true; + vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Pos.x, (int)viewport->Pos.y, (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags); + vd->WindowOwned = true; if (use_opengl) { - data->GLContext = SDL_GL_CreateContext(data->Window); + vd->GLContext = SDL_GL_CreateContext(vd->Window); SDL_GL_SetSwapInterval(0); } if (use_opengl && backup_context) - SDL_GL_MakeCurrent(data->Window, backup_context); + SDL_GL_MakeCurrent(vd->Window, backup_context); - viewport->PlatformHandle = (void*)data->Window; + viewport->PlatformHandle = (void*)vd->Window; #if defined(_WIN32) SDL_SysWMinfo info; SDL_VERSION(&info.version); - if (SDL_GetWindowWMInfo(data->Window, &info)) + if (SDL_GetWindowWMInfo(vd->Window, &info)) viewport->PlatformHandleRaw = info.info.win.window; #endif } static void ImGui_ImplSDL2_DestroyWindow(ImGuiViewport* viewport) { - if (ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData) + if (ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData) { - if (data->GLContext && data->WindowOwned) - SDL_GL_DeleteContext(data->GLContext); - if (data->Window && data->WindowOwned) - SDL_DestroyWindow(data->Window); - data->GLContext = NULL; - data->Window = NULL; - IM_DELETE(data); + if (vd->GLContext && vd->WindowOwned) + SDL_GL_DeleteContext(vd->GLContext); + if (vd->Window && vd->WindowOwned) + SDL_DestroyWindow(vd->Window); + vd->GLContext = NULL; + vd->Window = NULL; + IM_DELETE(vd); } viewport->PlatformUserData = viewport->PlatformHandle = NULL; } static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; #if defined(_WIN32) HWND hwnd = (HWND)viewport->PlatformHandleRaw; @@ -580,83 +630,83 @@ static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport) } #endif - SDL_ShowWindow(data->Window); + SDL_ShowWindow(vd->Window); } static ImVec2 ImGui_ImplSDL2_GetWindowPos(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; int x = 0, y = 0; - SDL_GetWindowPosition(data->Window, &x, &y); + SDL_GetWindowPosition(vd->Window, &x, &y); return ImVec2((float)x, (float)y); } static void ImGui_ImplSDL2_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - SDL_SetWindowPosition(data->Window, (int)pos.x, (int)pos.y); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + SDL_SetWindowPosition(vd->Window, (int)pos.x, (int)pos.y); } static ImVec2 ImGui_ImplSDL2_GetWindowSize(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; int w = 0, h = 0; - SDL_GetWindowSize(data->Window, &w, &h); + SDL_GetWindowSize(vd->Window, &w, &h); return ImVec2((float)w, (float)h); } static void ImGui_ImplSDL2_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - SDL_SetWindowSize(data->Window, (int)size.x, (int)size.y); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y); } static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - SDL_SetWindowTitle(data->Window, title); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + SDL_SetWindowTitle(vd->Window, title); } #if SDL_HAS_WINDOW_ALPHA static void ImGui_ImplSDL2_SetWindowAlpha(ImGuiViewport* viewport, float alpha) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - SDL_SetWindowOpacity(data->Window, alpha); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + SDL_SetWindowOpacity(vd->Window, alpha); } #endif static void ImGui_ImplSDL2_SetWindowFocus(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - SDL_RaiseWindow(data->Window); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + SDL_RaiseWindow(vd->Window); } static bool ImGui_ImplSDL2_GetWindowFocus(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - return (SDL_GetWindowFlags(data->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; } static bool ImGui_ImplSDL2_GetWindowMinimized(ImGuiViewport* viewport) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - return (SDL_GetWindowFlags(data->Window) & SDL_WINDOW_MINIMIZED) != 0; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_MINIMIZED) != 0; } static void ImGui_ImplSDL2_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - if (data->GLContext) - SDL_GL_MakeCurrent(data->Window, data->GLContext); + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + if (vd->GLContext) + SDL_GL_MakeCurrent(vd->Window, vd->GLContext); } static void ImGui_ImplSDL2_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; - if (data->GLContext) + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; + if (vd->GLContext) { - SDL_GL_MakeCurrent(data->Window, data->GLContext); - SDL_GL_SwapWindow(data->Window); + SDL_GL_MakeCurrent(vd->Window, vd->GLContext); + SDL_GL_SwapWindow(vd->Window); } } @@ -666,9 +716,9 @@ static void ImGui_ImplSDL2_SwapBuffers(ImGuiViewport* viewport, void*) #include static int ImGui_ImplSDL2_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_instance, const void* vk_allocator, ImU64* out_vk_surface) { - ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; (void)vk_allocator; - SDL_bool ret = SDL_Vulkan_CreateSurface(data->Window, (VkInstance)vk_instance, (VkSurfaceKHR*)out_vk_surface); + SDL_bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (VkSurfaceKHR*)out_vk_surface); return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY } #endif // SDL_HAS_VULKAN @@ -697,21 +747,16 @@ static void ImGui_ImplSDL2_InitPlatformInterface(SDL_Window* window, void* sdl_g platform_io.Platform_CreateVkSurface = ImGui_ImplSDL2_CreateVkSurface; #endif - // SDL2 by default doesn't pass mouse clicks to the application when the click focused a window. This is getting in the way of our interactions and we disable that behavior. -#if SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); -#endif - // Register main window handle (which is owned by the main application, not by us) // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - ImGuiViewportDataSDL2* data = IM_NEW(ImGuiViewportDataSDL2)(); - data->Window = window; - data->WindowID = SDL_GetWindowID(window); - data->WindowOwned = false; - data->GLContext = sdl_gl_context; - main_viewport->PlatformUserData = data; - main_viewport->PlatformHandle = data->Window; + ImGui_ImplSDL2_ViewportData* vd = IM_NEW(ImGui_ImplSDL2_ViewportData)(); + vd->Window = window; + vd->WindowID = SDL_GetWindowID(window); + vd->WindowOwned = false; + vd->GLContext = sdl_gl_context; + main_viewport->PlatformUserData = vd; + main_viewport->PlatformHandle = vd->Window; } static void ImGui_ImplSDL2_ShutdownPlatformInterface() diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_sdl.h b/examples/interactive/imgui-1.83/backends/imgui_impl_sdl.h similarity index 79% rename from examples/interactive/imgui-1.81/backends/imgui_impl_sdl.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_sdl.h index 6d8a0715..77c3ba2b 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_sdl.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_sdl.h @@ -12,7 +12,8 @@ // [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. // [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -27,5 +28,9 @@ IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window); IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window); IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(SDL_Window* window); +IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter +#endif diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.cpp similarity index 84% rename from examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.cpp index e7e528c8..96002fb1 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.cpp @@ -7,7 +7,8 @@ // Missing features: // [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this backend! See https://github.com/ocornut/imgui/pull/914 -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -23,6 +24,10 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-03-22: Vulkan: Fix mapped memory validation error when buffer sizes are not multiple of VkPhysicalDeviceLimits::nonCoherentAtomSize. +// 2021-02-18: Vulkan: Change blending equation to preserve alpha in output buffer. // 2021-01-27: Vulkan: Added support for custom function load and IMGUI_IMPL_VULKAN_NO_PROTOTYPES by using ImGui_ImplVulkan_LoadFunctions(). // 2020-11-11: Vulkan: Added support for specifying which subpass to reference during VkPipeline creation. // 2020-09-07: Vulkan: Added VkPipeline parameter to ImGui_ImplVulkan_RenderDrawData (default to one passed to ImGui_ImplVulkan_Init). @@ -76,41 +81,48 @@ struct ImGui_ImplVulkanH_WindowRenderBuffers // For multi-viewport support: // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataVulkan +struct ImGui_ImplVulkan_ViewportData { bool WindowOwned; ImGui_ImplVulkanH_Window Window; // Used by secondary viewports only ImGui_ImplVulkanH_WindowRenderBuffers RenderBuffers; // Used by all viewports - ImGuiViewportDataVulkan() { WindowOwned = false; memset(&RenderBuffers, 0, sizeof(RenderBuffers)); } - ~ImGuiViewportDataVulkan() { } + ImGui_ImplVulkan_ViewportData() { WindowOwned = false; memset(&RenderBuffers, 0, sizeof(RenderBuffers)); } + ~ImGui_ImplVulkan_ViewportData() { } }; // Vulkan data -static ImGui_ImplVulkan_InitInfo g_VulkanInitInfo = {}; -static VkRenderPass g_RenderPass = VK_NULL_HANDLE; -static VkDeviceSize g_BufferMemoryAlignment = 256; -static VkPipelineCreateFlags g_PipelineCreateFlags = 0x00; -static VkDescriptorSetLayout g_DescriptorSetLayout = VK_NULL_HANDLE; -static VkPipelineLayout g_PipelineLayout = VK_NULL_HANDLE; -static VkDescriptorSet g_DescriptorSet = VK_NULL_HANDLE; -static VkPipeline g_Pipeline = VK_NULL_HANDLE; -static uint32_t g_Subpass = 0; -static VkShaderModule g_ShaderModuleVert; -static VkShaderModule g_ShaderModuleFrag; -#ifdef VK_NO_PROTOTYPES -static bool g_FunctionsLoaded = false; -#else -static bool g_FunctionsLoaded = true; -#endif - -// Font data -static VkSampler g_FontSampler = VK_NULL_HANDLE; -static VkDeviceMemory g_FontMemory = VK_NULL_HANDLE; -static VkImage g_FontImage = VK_NULL_HANDLE; -static VkImageView g_FontView = VK_NULL_HANDLE; -static VkDeviceMemory g_UploadBufferMemory = VK_NULL_HANDLE; -static VkBuffer g_UploadBuffer = VK_NULL_HANDLE; +struct ImGui_ImplVulkan_Data +{ + ImGui_ImplVulkan_InitInfo VulkanInitInfo; + VkRenderPass RenderPass; + VkDeviceSize BufferMemoryAlignment; + VkPipelineCreateFlags PipelineCreateFlags; + VkDescriptorSetLayout DescriptorSetLayout; + VkPipelineLayout PipelineLayout; + VkDescriptorSet DescriptorSet; + VkPipeline Pipeline; + uint32_t Subpass; + VkShaderModule ShaderModuleVert; + VkShaderModule ShaderModuleFrag; + + // Font data + VkSampler FontSampler; + VkDeviceMemory FontMemory; + VkImage FontImage; + VkImageView FontView; + VkDeviceMemory UploadBufferMemory; + VkBuffer UploadBuffer; + + // Render buffers for main window + ImGui_ImplVulkanH_WindowRenderBuffers MainWindowRenderBuffers; + + ImGui_ImplVulkan_Data() + { + memset(this, 0, sizeof(*this)); + BufferMemoryAlignment = 256; + } +}; // Forward Declarations bool ImGui_ImplVulkan_CreateDeviceObjects(); @@ -126,6 +138,11 @@ void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_devi // Vulkan prototypes for use with custom loaders // (see description of IMGUI_IMPL_VULKAN_NO_PROTOTYPES in imgui_impl_vulkan.h #ifdef VK_NO_PROTOTYPES +static bool g_FunctionsLoaded = false; +#else +static bool g_FunctionsLoaded = true; +#endif +#ifdef VK_NO_PROTOTYPES #define IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_MAP_MACRO) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateCommandBuffers) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateDescriptorSets) \ @@ -184,7 +201,18 @@ void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_devi IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetSwapchainImagesKHR) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkMapMemory) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkUnmapMemory) \ - IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) + IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceSurfaceSupportKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkWaitForFences) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdBeginRenderPass) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdEndRenderPass) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkQueuePresentKHR) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkBeginCommandBuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkEndCommandBuffer) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkResetFences) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkQueueSubmit) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkResetCommandPool) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkAcquireNextImageKHR) // Define function pointers #define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; @@ -309,9 +337,18 @@ static uint32_t __glsl_shader_frag_spv[] = // FUNCTIONS //----------------------------------------------------------------------------- +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not tested and probably dysfunctional in this backend. +static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkPhysicalDeviceMemoryProperties prop; vkGetPhysicalDeviceMemoryProperties(v->PhysicalDevice, &prop); for (uint32_t i = 0; i < prop.memoryTypeCount; i++) @@ -322,21 +359,25 @@ static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, ui static void check_vk_result(VkResult err) { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (!bd) + return; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (v->CheckVkResultFn) v->CheckVkResultFn(err); } static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; if (buffer != VK_NULL_HANDLE) vkDestroyBuffer(v->Device, buffer, v->Allocator); if (buffer_memory != VK_NULL_HANDLE) vkFreeMemory(v->Device, buffer_memory, v->Allocator); - VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / g_BufferMemoryAlignment + 1) * g_BufferMemoryAlignment; + VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.size = vertex_buffer_size_aligned; @@ -347,7 +388,7 @@ static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory VkMemoryRequirements req; vkGetBufferMemoryRequirements(v->Device, buffer, &req); - g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; + bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; @@ -357,16 +398,18 @@ static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory err = vkBindBufferMemory(v->Device, buffer, buffer_memory, 0); check_vk_result(err); - p_buffer_size = new_size; + p_buffer_size = req.size; } static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pipeline, VkCommandBuffer command_buffer, ImGui_ImplVulkanH_FrameRenderBuffers* rb, int fb_width, int fb_height) { + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + // Bind pipeline and descriptor sets: { vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - VkDescriptorSet desc_set[1] = { g_DescriptorSet }; - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); + VkDescriptorSet desc_set[1] = { bd->DescriptorSet }; + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, desc_set, 0, NULL); } // Bind Vertex And Index Buffer: @@ -399,8 +442,8 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline float translate[2]; translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; - vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); - vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); } } @@ -413,12 +456,13 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm if (fb_width <= 0 || fb_height <= 0) return; - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (pipeline == VK_NULL_HANDLE) - pipeline = g_Pipeline; + pipeline = bd->Pipeline; // Allocate array to store enough vertex/index buffers. Each unique viewport gets its own storage. - ImGuiViewportDataVulkan* viewport_renderer_data = (ImGuiViewportDataVulkan*)draw_data->OwnerViewport->RendererUserData; + ImGui_ImplVulkan_ViewportData* viewport_renderer_data = (ImGui_ImplVulkan_ViewportData*)draw_data->OwnerViewport->RendererUserData; IM_ASSERT(viewport_renderer_data != NULL); ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &viewport_renderer_data->RenderBuffers; if (wrb->FrameRenderBuffers == NULL) @@ -445,9 +489,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Upload vertex/index data into a single contiguous GPU buffer ImDrawVert* vtx_dst = NULL; ImDrawIdx* idx_dst = NULL; - VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, vertex_size, 0, (void**)(&vtx_dst)); + VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, rb->VertexBufferSize, 0, (void**)(&vtx_dst)); check_vk_result(err); - err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, index_size, 0, (void**)(&idx_dst)); + err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, rb->IndexBufferSize, 0, (void**)(&idx_dst)); check_vk_result(err); for (int n = 0; n < draw_data->CmdListsCount; n++) { @@ -533,8 +577,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; unsigned char* pixels; int width, height; @@ -559,17 +604,17 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - err = vkCreateImage(v->Device, &info, v->Allocator, &g_FontImage); + err = vkCreateImage(v->Device, &info, v->Allocator, &bd->FontImage); check_vk_result(err); VkMemoryRequirements req; - vkGetImageMemoryRequirements(v->Device, g_FontImage, &req); + vkGetImageMemoryRequirements(v->Device, bd->FontImage, &req); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); - err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_FontMemory); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &bd->FontMemory); check_vk_result(err); - err = vkBindImageMemory(v->Device, g_FontImage, g_FontMemory, 0); + err = vkBindImageMemory(v->Device, bd->FontImage, bd->FontMemory, 0); check_vk_result(err); } @@ -577,25 +622,25 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) { VkImageViewCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - info.image = g_FontImage; + info.image = bd->FontImage; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = VK_FORMAT_R8G8B8A8_UNORM; info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; - err = vkCreateImageView(v->Device, &info, v->Allocator, &g_FontView); + err = vkCreateImageView(v->Device, &info, v->Allocator, &bd->FontView); check_vk_result(err); } // Update the Descriptor Set: { VkDescriptorImageInfo desc_image[1] = {}; - desc_image[0].sampler = g_FontSampler; - desc_image[0].imageView = g_FontView; + desc_image[0].sampler = bd->FontSampler; + desc_image[0].imageView = bd->FontView; desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkWriteDescriptorSet write_desc[1] = {}; write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write_desc[0].dstSet = g_DescriptorSet; + write_desc[0].dstSet = bd->DescriptorSet; write_desc[0].descriptorCount = 1; write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write_desc[0].pImageInfo = desc_image; @@ -609,34 +654,34 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) buffer_info.size = upload_size; buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &g_UploadBuffer); + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &bd->UploadBuffer); check_vk_result(err); VkMemoryRequirements req; - vkGetBufferMemoryRequirements(v->Device, g_UploadBuffer, &req); - g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; + vkGetBufferMemoryRequirements(v->Device, bd->UploadBuffer, &req); + bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); - err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_UploadBufferMemory); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &bd->UploadBufferMemory); check_vk_result(err); - err = vkBindBufferMemory(v->Device, g_UploadBuffer, g_UploadBufferMemory, 0); + err = vkBindBufferMemory(v->Device, bd->UploadBuffer, bd->UploadBufferMemory, 0); check_vk_result(err); } // Upload to Buffer: { char* map = NULL; - err = vkMapMemory(v->Device, g_UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); + err = vkMapMemory(v->Device, bd->UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); check_vk_result(err); memcpy(map, pixels, upload_size); VkMappedMemoryRange range[1] = {}; range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; - range[0].memory = g_UploadBufferMemory; + range[0].memory = bd->UploadBufferMemory; range[0].size = upload_size; err = vkFlushMappedMemoryRanges(v->Device, 1, range); check_vk_result(err); - vkUnmapMemory(v->Device, g_UploadBufferMemory); + vkUnmapMemory(v->Device, bd->UploadBufferMemory); } // Copy to Image: @@ -648,7 +693,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - copy_barrier[0].image = g_FontImage; + copy_barrier[0].image = bd->FontImage; copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy_barrier[0].subresourceRange.levelCount = 1; copy_barrier[0].subresourceRange.layerCount = 1; @@ -660,7 +705,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) region.imageExtent.width = width; region.imageExtent.height = height; region.imageExtent.depth = 1; - vkCmdCopyBufferToImage(command_buffer, g_UploadBuffer, g_FontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + vkCmdCopyBufferToImage(command_buffer, bd->UploadBuffer, bd->FontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); VkImageMemoryBarrier use_barrier[1] = {}; use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -670,7 +715,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - use_barrier[0].image = g_FontImage; + use_barrier[0].image = bd->FontImage; use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; use_barrier[0].subresourceRange.levelCount = 1; use_barrier[0].subresourceRange.layerCount = 1; @@ -678,7 +723,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) } // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)g_FontImage); + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontImage); return true; } @@ -686,29 +731,31 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator) { // Create the shader modules - if (g_ShaderModuleVert == NULL) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->ShaderModuleVert == NULL) { VkShaderModuleCreateInfo vert_info = {}; vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; vert_info.codeSize = sizeof(__glsl_shader_vert_spv); vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; - VkResult err = vkCreateShaderModule(device, &vert_info, allocator, &g_ShaderModuleVert); + VkResult err = vkCreateShaderModule(device, &vert_info, allocator, &bd->ShaderModuleVert); check_vk_result(err); } - if (g_ShaderModuleFrag == NULL) + if (bd->ShaderModuleFrag == NULL) { VkShaderModuleCreateInfo frag_info = {}; frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; frag_info.codeSize = sizeof(__glsl_shader_frag_spv); frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; - VkResult err = vkCreateShaderModule(device, &frag_info, allocator, &g_ShaderModuleFrag); + VkResult err = vkCreateShaderModule(device, &frag_info, allocator, &bd->ShaderModuleFrag); check_vk_result(err); } } static void ImGui_ImplVulkan_CreateFontSampler(VkDevice device, const VkAllocationCallbacks* allocator) { - if (g_FontSampler) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->FontSampler) return; VkSamplerCreateInfo info = {}; @@ -722,17 +769,18 @@ static void ImGui_ImplVulkan_CreateFontSampler(VkDevice device, const VkAllocati info.minLod = -1000; info.maxLod = 1000; info.maxAnisotropy = 1.0f; - VkResult err = vkCreateSampler(device, &info, allocator, &g_FontSampler); + VkResult err = vkCreateSampler(device, &info, allocator, &bd->FontSampler); check_vk_result(err); } static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDevice device, const VkAllocationCallbacks* allocator) { - if (g_DescriptorSetLayout) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->DescriptorSetLayout) return; ImGui_ImplVulkan_CreateFontSampler(device, allocator); - VkSampler sampler[1] = { g_FontSampler }; + VkSampler sampler[1] = { bd->FontSampler }; VkDescriptorSetLayoutBinding binding[1] = {}; binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding[0].descriptorCount = 1; @@ -742,13 +790,14 @@ static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDevice device, const Vk info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 1; info.pBindings = binding; - VkResult err = vkCreateDescriptorSetLayout(device, &info, allocator, &g_DescriptorSetLayout); + VkResult err = vkCreateDescriptorSetLayout(device, &info, allocator, &bd->DescriptorSetLayout); check_vk_result(err); } static void ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device, const VkAllocationCallbacks* allocator) { - if (g_PipelineLayout) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->PipelineLayout) return; // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix @@ -757,29 +806,30 @@ static void ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device, const VkAlloc push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; push_constants[0].offset = sizeof(float) * 0; push_constants[0].size = sizeof(float) * 4; - VkDescriptorSetLayout set_layout[1] = { g_DescriptorSetLayout }; + VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.setLayoutCount = 1; layout_info.pSetLayouts = set_layout; layout_info.pushConstantRangeCount = 1; layout_info.pPushConstantRanges = push_constants; - VkResult err = vkCreatePipelineLayout(device, &layout_info, allocator, &g_PipelineLayout); + VkResult err = vkCreatePipelineLayout(device, &layout_info, allocator, &bd->PipelineLayout); check_vk_result(err); } static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline, uint32_t subpass) { + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_CreateShaderModules(device, allocator); VkPipelineShaderStageCreateInfo stage[2] = {}; stage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stage[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - stage[0].module = g_ShaderModuleVert; + stage[0].module = bd->ShaderModuleVert; stage[0].pName = "main"; stage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - stage[1].module = g_ShaderModuleFrag; + stage[1].module = bd->ShaderModuleFrag; stage[1].pName = "main"; VkVertexInputBindingDescription binding_desc[1] = {}; @@ -832,8 +882,8 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; color_attachment[0].colorBlendOp = VK_BLEND_OP_ADD; - color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; color_attachment[0].alphaBlendOp = VK_BLEND_OP_ADD; color_attachment[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; @@ -855,7 +905,7 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - info.flags = g_PipelineCreateFlags; + info.flags = bd->PipelineCreateFlags; info.stageCount = 2; info.pStages = stage; info.pVertexInputState = &vertex_info; @@ -866,7 +916,7 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC info.pDepthStencilState = &depth_info; info.pColorBlendState = &blend_info; info.pDynamicState = &dynamic_state; - info.layout = g_PipelineLayout; + info.layout = bd->PipelineLayout; info.renderPass = renderPass; info.subpass = subpass; VkResult err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &info, allocator, pipeline); @@ -875,10 +925,11 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC bool ImGui_ImplVulkan_CreateDeviceObjects() { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; - if (!g_FontSampler) + if (!bd->FontSampler) { VkSamplerCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; @@ -891,13 +942,13 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.minLod = -1000; info.maxLod = 1000; info.maxAnisotropy = 1.0f; - err = vkCreateSampler(v->Device, &info, v->Allocator, &g_FontSampler); + err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->FontSampler); check_vk_result(err); } - if (!g_DescriptorSetLayout) + if (!bd->DescriptorSetLayout) { - VkSampler sampler[1] = {g_FontSampler}; + VkSampler sampler[1] = {bd->FontSampler}; VkDescriptorSetLayoutBinding binding[1] = {}; binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding[0].descriptorCount = 1; @@ -907,7 +958,7 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 1; info.pBindings = binding; - err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &g_DescriptorSetLayout); + err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &bd->DescriptorSetLayout); check_vk_result(err); } @@ -917,64 +968,66 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; alloc_info.descriptorPool = v->DescriptorPool; alloc_info.descriptorSetCount = 1; - alloc_info.pSetLayouts = &g_DescriptorSetLayout; - err = vkAllocateDescriptorSets(v->Device, &alloc_info, &g_DescriptorSet); + alloc_info.pSetLayouts = &bd->DescriptorSetLayout; + err = vkAllocateDescriptorSets(v->Device, &alloc_info, &bd->DescriptorSet); check_vk_result(err); } - if (!g_PipelineLayout) + if (!bd->PipelineLayout) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix VkPushConstantRange push_constants[1] = {}; push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; push_constants[0].offset = sizeof(float) * 0; push_constants[0].size = sizeof(float) * 4; - VkDescriptorSetLayout set_layout[1] = { g_DescriptorSetLayout }; + VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.setLayoutCount = 1; layout_info.pSetLayouts = set_layout; layout_info.pushConstantRangeCount = 1; layout_info.pPushConstantRanges = push_constants; - err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &g_PipelineLayout); + err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &bd->PipelineLayout); check_vk_result(err); } - ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, g_RenderPass, v->MSAASamples, &g_Pipeline, g_Subpass); + ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, bd->RenderPass, v->MSAASamples, &bd->Pipeline, bd->Subpass); return true; } void ImGui_ImplVulkan_DestroyFontUploadObjects() { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; - if (g_UploadBuffer) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + if (bd->UploadBuffer) { - vkDestroyBuffer(v->Device, g_UploadBuffer, v->Allocator); - g_UploadBuffer = VK_NULL_HANDLE; + vkDestroyBuffer(v->Device, bd->UploadBuffer, v->Allocator); + bd->UploadBuffer = VK_NULL_HANDLE; } - if (g_UploadBufferMemory) + if (bd->UploadBufferMemory) { - vkFreeMemory(v->Device, g_UploadBufferMemory, v->Allocator); - g_UploadBufferMemory = VK_NULL_HANDLE; + vkFreeMemory(v->Device, bd->UploadBufferMemory, v->Allocator); + bd->UploadBufferMemory = VK_NULL_HANDLE; } } void ImGui_ImplVulkan_DestroyDeviceObjects() { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator); ImGui_ImplVulkan_DestroyFontUploadObjects(); - if (g_ShaderModuleVert) { vkDestroyShaderModule(v->Device, g_ShaderModuleVert, v->Allocator); g_ShaderModuleVert = VK_NULL_HANDLE; } - if (g_ShaderModuleFrag) { vkDestroyShaderModule(v->Device, g_ShaderModuleFrag, v->Allocator); g_ShaderModuleFrag = VK_NULL_HANDLE; } - if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } - if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } - if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } - if (g_FontSampler) { vkDestroySampler(v->Device, g_FontSampler, v->Allocator); g_FontSampler = VK_NULL_HANDLE; } - if (g_DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, g_DescriptorSetLayout, v->Allocator); g_DescriptorSetLayout = VK_NULL_HANDLE; } - if (g_PipelineLayout) { vkDestroyPipelineLayout(v->Device, g_PipelineLayout, v->Allocator); g_PipelineLayout = VK_NULL_HANDLE; } - if (g_Pipeline) { vkDestroyPipeline(v->Device, g_Pipeline, v->Allocator); g_Pipeline = VK_NULL_HANDLE; } + if (bd->ShaderModuleVert) { vkDestroyShaderModule(v->Device, bd->ShaderModuleVert, v->Allocator); bd->ShaderModuleVert = VK_NULL_HANDLE; } + if (bd->ShaderModuleFrag) { vkDestroyShaderModule(v->Device, bd->ShaderModuleFrag, v->Allocator); bd->ShaderModuleFrag = VK_NULL_HANDLE; } + if (bd->FontView) { vkDestroyImageView(v->Device, bd->FontView, v->Allocator); bd->FontView = VK_NULL_HANDLE; } + if (bd->FontImage) { vkDestroyImage(v->Device, bd->FontImage, v->Allocator); bd->FontImage = VK_NULL_HANDLE; } + if (bd->FontMemory) { vkFreeMemory(v->Device, bd->FontMemory, v->Allocator); bd->FontMemory = VK_NULL_HANDLE; } + if (bd->FontSampler) { vkDestroySampler(v->Device, bd->FontSampler, v->Allocator); bd->FontSampler = VK_NULL_HANDLE; } + if (bd->DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, bd->DescriptorSetLayout, v->Allocator); bd->DescriptorSetLayout = VK_NULL_HANDLE; } + if (bd->PipelineLayout) { vkDestroyPipelineLayout(v->Device, bd->PipelineLayout, v->Allocator); bd->PipelineLayout = VK_NULL_HANDLE; } + if (bd->Pipeline) { vkDestroyPipeline(v->Device, bd->Pipeline, v->Allocator); bd->Pipeline = VK_NULL_HANDLE; } } bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data) @@ -1002,8 +1055,12 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend { IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); - // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplVulkan_Data* bd = IM_NEW(ImGui_ImplVulkan_Data)(); + io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_vulkan"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) @@ -1017,15 +1074,15 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend IM_ASSERT(info->ImageCount >= info->MinImageCount); IM_ASSERT(render_pass != VK_NULL_HANDLE); - g_VulkanInitInfo = *info; - g_RenderPass = render_pass; - g_Subpass = info->Subpass; + bd->VulkanInitInfo = *info; + bd->RenderPass = render_pass; + bd->Subpass = info->Subpass; ImGui_ImplVulkan_CreateDeviceObjects(); // Our render function expect RendererUserData to be storing the window render buffer we need (for the main viewport we won't use ->Window) ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - main_viewport->RendererUserData = IM_NEW(ImGuiViewportDataVulkan)(); + main_viewport->RendererUserData = IM_NEW(ImGui_ImplVulkan_ViewportData)(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplVulkan_InitPlatformInterface(); @@ -1035,36 +1092,47 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend void ImGui_ImplVulkan_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + // First destroy objects in all viewports ImGui_ImplVulkan_DestroyDeviceObjects(); // Manually delete main viewport render data in-case we haven't initialized for viewports ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - if (ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)main_viewport->RendererUserData) - IM_DELETE(data); + if (ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)main_viewport->RendererUserData) + IM_DELETE(vd); main_viewport->RendererUserData = NULL; // Clean up windows ImGui_ImplVulkan_ShutdownPlatformInterface(); + + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); } void ImGui_ImplVulkan_NewFrame() { + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplVulkan_Init()?"); + IM_UNUSED(bd); } void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) { + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); IM_ASSERT(min_image_count >= 2); - if (g_VulkanInitInfo.MinImageCount == min_image_count) + if (bd->VulkanInitInfo.MinImageCount == min_image_count) return; IM_ASSERT(0); // FIXME-VIEWPORT: Unsupported. Need to recreate all swap chains! - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err = vkDeviceWaitIdle(v->Device); check_vk_result(err); ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator); - g_VulkanInitInfo.MinImageCount = min_image_count; + bd->VulkanInitInfo.MinImageCount = min_image_count; } @@ -1334,7 +1402,7 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V // We do not create a pipeline by default as this is also used by examples' main.cpp, // but secondary viewport in multi-viewport mode may want to create one with: - //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, g_Subpass); + //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, bd->Subpass); } // Create The Image Views @@ -1392,7 +1460,7 @@ void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevic void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator) { vkDeviceWaitIdle(device); // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals) - //vkQueueWaitIdle(g_Queue); + //vkQueueWaitIdle(bd->Queue); for (uint32_t i = 0; i < wd->ImageCount; i++) { @@ -1455,8 +1523,8 @@ void ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(VkDevice device, const V { ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); for (int n = 0; n < platform_io.Viewports.Size; n++) - if (ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)platform_io.Viewports[n]->RendererUserData) - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(device, &data->RenderBuffers, allocator); + if (ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)platform_io.Viewports[n]->RendererUserData) + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(device, &vd->RenderBuffers, allocator); } //-------------------------------------------------------------------------------------------------------- @@ -1467,10 +1535,11 @@ void ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(VkDevice device, const V static void ImGui_ImplVulkan_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataVulkan* data = IM_NEW(ImGuiViewportDataVulkan)(); - viewport->RendererUserData = data; - ImGui_ImplVulkanH_Window* wd = &data->Window; - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_ViewportData* vd = IM_NEW(ImGui_ImplVulkan_ViewportData)(); + viewport->RendererUserData = vd; + ImGui_ImplVulkanH_Window* wd = &vd->Window; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; // Create surface ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); @@ -1500,43 +1569,51 @@ static void ImGui_ImplVulkan_CreateWindow(ImGuiViewport* viewport) // Create SwapChain, RenderPass, Framebuffer, etc. wd->ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; ImGui_ImplVulkanH_CreateOrResizeWindow(v->Instance, v->PhysicalDevice, v->Device, wd, v->QueueFamily, v->Allocator, (int)viewport->Size.x, (int)viewport->Size.y, v->MinImageCount); - data->WindowOwned = true; + vd->WindowOwned = true; } static void ImGui_ImplVulkan_DestroyWindow(ImGuiViewport* viewport) { // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. - if (ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)viewport->RendererUserData) { - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; - if (data->WindowOwned) - ImGui_ImplVulkanH_DestroyWindow(v->Instance, v->Device, &data->Window, v->Allocator); - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &data->RenderBuffers, v->Allocator); - IM_DELETE(data); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + if (vd->WindowOwned) + ImGui_ImplVulkanH_DestroyWindow(v->Instance, v->Device, &vd->Window, v->Allocator); + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &vd->RenderBuffers, v->Allocator); + IM_DELETE(vd); } viewport->RendererUserData = NULL; } static void ImGui_ImplVulkan_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; - if (data == NULL) // This is NULL for the main viewport (which is left to the user/app to handle) + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)viewport->RendererUserData; + if (vd == NULL) // This is NULL for the main viewport (which is left to the user/app to handle) return; - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; - data->Window.ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; - ImGui_ImplVulkanH_CreateOrResizeWindow(v->Instance, v->PhysicalDevice, v->Device, &data->Window, v->QueueFamily, v->Allocator, (int)size.x, (int)size.y, v->MinImageCount); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + vd->Window.ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; + ImGui_ImplVulkanH_CreateOrResizeWindow(v->Instance, v->PhysicalDevice, v->Device, &vd->Window, v->QueueFamily, v->Allocator, (int)size.x, (int)size.y, v->MinImageCount); } static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) { - ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; - ImGui_ImplVulkanH_Window* wd = &data->Window; - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)viewport->RendererUserData; + ImGui_ImplVulkanH_Window* wd = &vd->Window; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[wd->SemaphoreIndex]; { + { + err = vkAcquireNextImageKHR(v->Device, wd->Swapchain, UINT64_MAX, fsd->ImageAcquiredSemaphore, VK_NULL_HANDLE, &wd->FrameIndex); + check_vk_result(err); + fd = &wd->Frames[wd->FrameIndex]; + } for (;;) { err = vkWaitForFences(v->Device, 1, &fd->Fence, VK_TRUE, 100); @@ -1544,11 +1621,6 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) if (err == VK_TIMEOUT) continue; check_vk_result(err); } - { - err = vkAcquireNextImageKHR(v->Device, wd->Swapchain, UINT64_MAX, fsd->ImageAcquiredSemaphore, VK_NULL_HANDLE, &wd->FrameIndex); - check_vk_result(err); - fd = &wd->Frames[wd->FrameIndex]; - } { err = vkResetCommandPool(v->Device, fd->CommandPool, 0); check_vk_result(err); @@ -1602,9 +1674,10 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) static void ImGui_ImplVulkan_SwapBuffers(ImGuiViewport* viewport, void*) { - ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; - ImGui_ImplVulkanH_Window* wd = &data->Window; - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_ViewportData* vd = (ImGui_ImplVulkan_ViewportData*)viewport->RendererUserData; + ImGui_ImplVulkanH_Window* wd = &vd->Window; + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; uint32_t present_index = wd->FrameIndex; @@ -1618,8 +1691,8 @@ static void ImGui_ImplVulkan_SwapBuffers(ImGuiViewport* viewport, void*) info.pSwapchains = &wd->Swapchain; info.pImageIndices = &present_index; err = vkQueuePresentKHR(v->Queue, &info); - if (err == VK_ERROR_OUT_OF_DATE_KHR) - ImGui_ImplVulkanH_CreateOrResizeWindow(v->Instance, v->PhysicalDevice, v->Device, &data->Window, v->QueueFamily, v->Allocator, (int)viewport->Size.x, (int)viewport->Size.y, v->MinImageCount); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) + ImGui_ImplVulkanH_CreateOrResizeWindow(v->Instance, v->PhysicalDevice, v->Device, &vd->Window, v->QueueFamily, v->Allocator, (int)viewport->Size.x, (int)viewport->Size.y, v->MinImageCount); else check_vk_result(err); diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.h b/examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.h similarity index 97% rename from examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.h index de4e2820..58b68f08 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_vulkan.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_vulkan.h @@ -7,7 +7,8 @@ // [ ] Platform: Multi-viewport / platform windows. // [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this backend! See https://github.com/ocornut/imgui/pull/914 -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.cpp similarity index 74% rename from examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.cpp index 089dbfd7..473154c5 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.cpp @@ -6,12 +6,17 @@ // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021-05-24: Add support for draw_data->FramebufferScale. +// 2021-05-19: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-05-16: Update to latest WebGPU specs (compatible with Emscripten 2.0.20 and Chrome Canary 92). +// 2021-02-18: Change blending equation to preserve alpha in output buffer. // 2021-01-28: Initial version. #include "imgui.h" @@ -21,11 +26,16 @@ #define HAS_EMSCRIPTEN_VERSION(major, minor, tiny) (__EMSCRIPTEN_major__ > (major) || (__EMSCRIPTEN_major__ == (major) && __EMSCRIPTEN_minor__ > (minor)) || (__EMSCRIPTEN_major__ == (major) && __EMSCRIPTEN_minor__ == (minor) && __EMSCRIPTEN_tiny__ >= (tiny))) +#if defined(__EMSCRIPTEN__) && !HAS_EMSCRIPTEN_VERSION(2, 0, 20) +#error "Requires at least emscripten 2.0.20" +#endif + // Dear ImGui prototypes from imgui_internal.h extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed = 0); // WebGPU data static WGPUDevice g_wgpuDevice = NULL; +static WGPUQueue g_defaultQueue = NULL; static WGPUTextureFormat g_renderTargetFormat = WGPUTextureFormat_Undefined; static WGPURenderPipeline g_pipelineState = NULL; @@ -36,9 +46,9 @@ struct RenderResources WGPUSampler Sampler; // Sampler for the font texture WGPUBuffer Uniforms; // Shader uniforms WGPUBindGroup CommonBindGroup; // Resources bind-group to bind the common resources to pipeline - WGPUBindGroupLayout ImageBindGroupLayout; // Bind group layout for image textures ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map) WGPUBindGroup ImageBindGroup; // Default font-resource of Dear ImGui + WGPUBindGroupLayout ImageBindGroupLayout; // Cache layout used for the image bind group. Avoids allocating unnecessary JS objects when working with WebASM }; static RenderResources g_resources; @@ -240,8 +250,8 @@ static void SafeRelease(RenderResources& res) SafeRelease(res.Sampler); SafeRelease(res.Uniforms); SafeRelease(res.CommonBindGroup); - SafeRelease(res.ImageBindGroupLayout); SafeRelease(res.ImageBindGroup); + SafeRelease(res.ImageBindGroupLayout); }; static void SafeRelease(FrameResources& res) @@ -295,23 +305,21 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas { 0.0f, 0.0f, 0.5f, 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, }; - wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), g_resources.Uniforms, 0, mvp, sizeof(mvp)); + wgpuQueueWriteBuffer(g_defaultQueue, g_resources.Uniforms, 0, mvp, sizeof(mvp)); } // Setup viewport - wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->DisplaySize.x, draw_data->DisplaySize.y, 0, 1); + wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->FramebufferScale.x * draw_data->DisplaySize.x, draw_data->FramebufferScale.y * draw_data->DisplaySize.y, 0, 1); // Bind shader and vertex buffers - unsigned int stride = sizeof(ImDrawVert); - unsigned int offset = 0; - wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, offset, fr->VertexBufferSize * stride); - wgpuRenderPassEncoderSetIndexBuffer(ctx, fr->IndexBuffer, sizeof(ImDrawIdx) == 2 ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32, 0, fr->IndexBufferSize * sizeof(ImDrawIdx)); + wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, 0); + wgpuRenderPassEncoderSetIndexBuffer(ctx, fr->IndexBuffer, sizeof(ImDrawIdx) == 2 ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32, 0, 0); wgpuRenderPassEncoderSetPipeline(ctx, g_pipelineState); wgpuRenderPassEncoderSetBindGroup(ctx, 0, g_resources.CommonBindGroup, 0, NULL); // Setup blend factor WGPUColor blend_color = { 0.f, 0.f, 0.f, 0.f }; - wgpuRenderPassEncoderSetBlendColor(ctx, &blend_color); + wgpuRenderPassEncoderSetBlendConstant(ctx, &blend_color); } // Render function @@ -330,7 +338,11 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder // Create and grow vertex/index buffers if needed if (fr->VertexBuffer == NULL || fr->VertexBufferSize < draw_data->TotalVtxCount) { - SafeRelease(fr->VertexBuffer); + if (fr->VertexBuffer) + { + wgpuBufferDestroy(fr->VertexBuffer); + wgpuBufferRelease(fr->VertexBuffer); + } SafeRelease(fr->VertexBufferHost); fr->VertexBufferSize = draw_data->TotalVtxCount + 5000; @@ -350,7 +362,11 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder } if (fr->IndexBuffer == NULL || fr->IndexBufferSize < draw_data->TotalIdxCount) { - SafeRelease(fr->IndexBuffer); + if (fr->IndexBuffer) + { + wgpuBufferDestroy(fr->IndexBuffer); + wgpuBufferRelease(fr->IndexBuffer); + } SafeRelease(fr->IndexBufferHost); fr->IndexBufferSize = draw_data->TotalIdxCount + 10000; @@ -382,8 +398,8 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder } int64_t vb_write_size = ((char*)vtx_dst - (char*)fr->VertexBufferHost + 3) & ~3; int64_t ib_write_size = ((char*)idx_dst - (char*)fr->IndexBufferHost + 3) & ~3; - wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), fr->VertexBuffer, 0, fr->VertexBufferHost, vb_write_size); - wgpuQueueWriteBuffer(wgpuDeviceGetDefaultQueue(g_wgpuDevice), fr->IndexBuffer, 0, fr->IndexBufferHost, ib_write_size); + wgpuQueueWriteBuffer(g_defaultQueue, fr->VertexBuffer, 0, fr->VertexBufferHost, vb_write_size); + wgpuQueueWriteBuffer(g_defaultQueue, fr->IndexBuffer, 0, fr->IndexBufferHost, ib_write_size); // Setup desired render state ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr); @@ -392,6 +408,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder // (Because we merged all buffers into a single one, we maintain our own offset into them) int global_vtx_offset = 0; int global_idx_offset = 0; + ImVec2 clip_scale = draw_data->FramebufferScale; ImVec2 clip_off = draw_data->DisplayPos; for (int n = 0; n < draw_data->CmdListsCount; n++) { @@ -411,24 +428,26 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder else { // Bind custom texture - auto bind_group = g_resources.ImageBindGroups.GetVoidPtr(ImHashData(&pcmd->TextureId, sizeof(ImTextureID))); + ImTextureID tex_id = pcmd->GetTexID(); + ImGuiID tex_id_hash = ImHashData(&tex_id, sizeof(tex_id)); + auto bind_group = g_resources.ImageBindGroups.GetVoidPtr(tex_id_hash); if (bind_group) { wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, (WGPUBindGroup)bind_group, 0, NULL); } else { - WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(g_resources.ImageBindGroupLayout, (WGPUTextureView)pcmd->TextureId); - g_resources.ImageBindGroups.SetVoidPtr(ImHashData(&pcmd->TextureId, sizeof(ImTextureID)), image_bind_group); + WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(g_resources.ImageBindGroupLayout, (WGPUTextureView)tex_id); + g_resources.ImageBindGroups.SetVoidPtr(tex_id_hash, image_bind_group); wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, image_bind_group, 0, NULL); } // Apply Scissor, Bind texture, Draw uint32_t clip_rect[4]; - clip_rect[0] = static_cast(pcmd->ClipRect.x - clip_off.x); - clip_rect[1] = static_cast(pcmd->ClipRect.y - clip_off.y); - clip_rect[2] = static_cast(pcmd->ClipRect.z - clip_off.x); - clip_rect[3] = static_cast(pcmd->ClipRect.w - clip_off.y); + clip_rect[0] = (uint32_t)(clip_scale.x * (pcmd->ClipRect.x - clip_off.x)); + clip_rect[1] = (uint32_t)(clip_scale.y * (pcmd->ClipRect.y - clip_off.y)); + clip_rect[2] = (uint32_t)(clip_scale.x * (pcmd->ClipRect.z - clip_off.x)); + clip_rect[3] = (uint32_t)(clip_scale.y * (pcmd->ClipRect.w - clip_off.y)); wgpuRenderPassEncoderSetScissorRect(pass_encoder, clip_rect[0], clip_rect[1], clip_rect[2] - clip_rect[0], clip_rect[3] - clip_rect[1]); wgpuRenderPassEncoderDrawIndexed(pass_encoder, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); } @@ -438,18 +457,6 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder } } -static WGPUBuffer ImGui_ImplWGPU_CreateBufferFromData(const WGPUDevice& device, const void* data, uint64_t size, WGPUBufferUsage usage) -{ - WGPUBufferDescriptor descriptor = {}; - descriptor.size = size; - descriptor.usage = usage | WGPUBufferUsage_CopyDst; - WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); - - WGPUQueue queue = wgpuDeviceGetDefaultQueue(g_wgpuDevice); - wgpuQueueWriteBuffer(queue, buffer, 0, data, size); - return buffer; -} - static void ImGui_ImplWGPU_CreateFontsTexture() { // Build texture atlas @@ -465,7 +472,7 @@ static void ImGui_ImplWGPU_CreateFontsTexture() tex_desc.dimension = WGPUTextureDimension_2D; tex_desc.size.width = width; tex_desc.size.height = height; - tex_desc.size.depth = 1; + tex_desc.size.depthOrArrayLayers = 1; tex_desc.sampleCount = 1; tex_desc.format = WGPUTextureFormat_RGBA8Unorm; tex_desc.mipLevelCount = 1; @@ -485,34 +492,17 @@ static void ImGui_ImplWGPU_CreateFontsTexture() // Upload texture data { - WGPUBuffer staging_buffer = ImGui_ImplWGPU_CreateBufferFromData(g_wgpuDevice, pixels, (uint32_t)(width * size_pp * height), WGPUBufferUsage_CopySrc); - - WGPUBufferCopyView bufferCopyView = {}; - bufferCopyView.buffer = staging_buffer; - bufferCopyView.layout.offset = 0; - bufferCopyView.layout.bytesPerRow = width * size_pp; - bufferCopyView.layout.rowsPerImage = height; - - WGPUTextureCopyView textureCopyView = {}; - textureCopyView.texture = g_resources.FontTexture; - textureCopyView.mipLevel = 0; - textureCopyView.origin = { 0, 0, 0 }; -#if !defined(__EMSCRIPTEN__) || HAS_EMSCRIPTEN_VERSION(2, 0, 14) - textureCopyView.aspect = WGPUTextureAspect_All; -#endif - - WGPUExtent3D copySize = { (uint32_t)width, (uint32_t)height, 1 }; - - WGPUCommandEncoderDescriptor enc_desc = {}; - WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(g_wgpuDevice, &enc_desc); - wgpuCommandEncoderCopyBufferToTexture(encoder, &bufferCopyView, &textureCopyView, ©Size); - WGPUCommandBufferDescriptor cmd_buf_desc = {}; - WGPUCommandBuffer copy = wgpuCommandEncoderFinish(encoder, &cmd_buf_desc); - WGPUQueue queue = wgpuDeviceGetDefaultQueue(g_wgpuDevice); - wgpuQueueSubmit(queue, 1, ©); - - wgpuCommandEncoderRelease(encoder); - wgpuBufferRelease(staging_buffer); + WGPUImageCopyTexture dst_view = {}; + dst_view.texture = g_resources.FontTexture; + dst_view.mipLevel = 0; + dst_view.origin = { 0, 0, 0 }; + dst_view.aspect = WGPUTextureAspect_All; + WGPUTextureDataLayout layout = {}; + layout.offset = 0; + layout.bytesPerRow = width * size_pp; + layout.rowsPerImage = height; + WGPUExtent3D size = { (uint32_t)width, (uint32_t)height, 1 }; + wgpuQueueWriteTexture(g_defaultQueue, &dst_view, pixels, (uint32_t)(width * size_pp * height), &layout, &size); } // Create the associated sampler @@ -524,9 +514,7 @@ static void ImGui_ImplWGPU_CreateFontsTexture() sampler_desc.addressModeU = WGPUAddressMode_Repeat; sampler_desc.addressModeV = WGPUAddressMode_Repeat; sampler_desc.addressModeW = WGPUAddressMode_Repeat; -#if !defined(__EMSCRIPTEN__) || HAS_EMSCRIPTEN_VERSION(2, 0, 14) sampler_desc.maxAnisotropy = 1; -#endif g_resources.Sampler = wgpuDeviceCreateSampler(g_wgpuDevice, &sampler_desc); } @@ -556,139 +544,82 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() ImGui_ImplWGPU_InvalidateDeviceObjects(); // Create render pipeline - WGPURenderPipelineDescriptor graphics_pipeline_desc = {}; - graphics_pipeline_desc.primitiveTopology = WGPUPrimitiveTopology_TriangleList; - graphics_pipeline_desc.sampleCount = 1; - graphics_pipeline_desc.sampleMask = UINT_MAX; - - WGPUBindGroupLayoutEntry common_bg_layout_entries[2] = {}; - common_bg_layout_entries[0].binding = 0; - common_bg_layout_entries[0].visibility = WGPUShaderStage_Vertex; -#if !defined(__EMSCRIPTEN__) || HAS_EMSCRIPTEN_VERSION(2, 0, 14) - common_bg_layout_entries[0].buffer.type = WGPUBufferBindingType_Uniform; -#else - common_bg_layout_entries[0].type = WGPUBindingType_UniformBuffer; -#endif - common_bg_layout_entries[1].binding = 1; - common_bg_layout_entries[1].visibility = WGPUShaderStage_Fragment; -#if !defined(__EMSCRIPTEN__) || HAS_EMSCRIPTEN_VERSION(2, 0, 14) - common_bg_layout_entries[1].sampler.type = WGPUSamplerBindingType_Filtering; -#else - common_bg_layout_entries[1].type = WGPUBindingType_Sampler; -#endif - - WGPUBindGroupLayoutEntry image_bg_layout_entries[1] = {}; - image_bg_layout_entries[0].binding = 0; - image_bg_layout_entries[0].visibility = WGPUShaderStage_Fragment; -#if !defined(__EMSCRIPTEN__) || HAS_EMSCRIPTEN_VERSION(2, 0, 14) - image_bg_layout_entries[0].texture.sampleType = WGPUTextureSampleType_Float; - image_bg_layout_entries[0].texture.viewDimension = WGPUTextureViewDimension_2D; -#else - image_bg_layout_entries[0].type = WGPUBindingType_SampledTexture; -#endif - - WGPUBindGroupLayoutDescriptor common_bg_layout_desc = {}; - common_bg_layout_desc.entryCount = 2; - common_bg_layout_desc.entries = common_bg_layout_entries; - - WGPUBindGroupLayoutDescriptor image_bg_layout_desc = {}; - image_bg_layout_desc.entryCount = 1; - image_bg_layout_desc.entries = image_bg_layout_entries; - - WGPUBindGroupLayout bg_layouts[2]; - bg_layouts[0] = wgpuDeviceCreateBindGroupLayout(g_wgpuDevice, &common_bg_layout_desc); - bg_layouts[1] = wgpuDeviceCreateBindGroupLayout(g_wgpuDevice, &image_bg_layout_desc); - - WGPUPipelineLayoutDescriptor layout_desc = {}; - layout_desc.bindGroupLayoutCount = 2; - layout_desc.bindGroupLayouts = bg_layouts; - graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(g_wgpuDevice, &layout_desc); + WGPURenderPipelineDescriptor2 graphics_pipeline_desc = {}; + graphics_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + graphics_pipeline_desc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; + graphics_pipeline_desc.primitive.frontFace = WGPUFrontFace_CW; + graphics_pipeline_desc.primitive.cullMode = WGPUCullMode_None; + graphics_pipeline_desc.multisample.count = 1; + graphics_pipeline_desc.multisample.mask = UINT_MAX; + graphics_pipeline_desc.multisample.alphaToCoverageEnabled = false; + graphics_pipeline_desc.layout = nullptr; // Use automatic layout generation // Create the vertex shader WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__glsl_shader_vert_spv, sizeof(__glsl_shader_vert_spv) / sizeof(uint32_t)); - graphics_pipeline_desc.vertexStage = vertex_shader_desc; + graphics_pipeline_desc.vertex.module = vertex_shader_desc.module; + graphics_pipeline_desc.vertex.entryPoint = vertex_shader_desc.entryPoint; // Vertex input configuration - WGPUVertexAttributeDescriptor attribute_binding_desc[] = + WGPUVertexAttribute attribute_desc[] = { - { WGPUVertexFormat_Float2, (uint64_t)IM_OFFSETOF(ImDrawVert, pos), 0 }, - { WGPUVertexFormat_Float2, (uint64_t)IM_OFFSETOF(ImDrawVert, uv), 1 }, - { WGPUVertexFormat_UChar4Norm, (uint64_t)IM_OFFSETOF(ImDrawVert, col), 2 }, + { WGPUVertexFormat_Float32x2, (uint64_t)IM_OFFSETOF(ImDrawVert, pos), 0 }, + { WGPUVertexFormat_Float32x2, (uint64_t)IM_OFFSETOF(ImDrawVert, uv), 1 }, + { WGPUVertexFormat_Unorm8x4, (uint64_t)IM_OFFSETOF(ImDrawVert, col), 2 }, }; - WGPUVertexBufferLayoutDescriptor buffer_binding_desc; - buffer_binding_desc.arrayStride = sizeof(ImDrawVert); - buffer_binding_desc.stepMode = WGPUInputStepMode_Vertex; - buffer_binding_desc.attributeCount = 3; - buffer_binding_desc.attributes = attribute_binding_desc; + WGPUVertexBufferLayout buffer_layouts[1]; + buffer_layouts[0].arrayStride = sizeof(ImDrawVert); + buffer_layouts[0].stepMode = WGPUInputStepMode_Vertex; + buffer_layouts[0].attributeCount = 3; + buffer_layouts[0].attributes = attribute_desc; - WGPUVertexStateDescriptor vertex_state_desc = {}; - vertex_state_desc.indexFormat = WGPUIndexFormat_Undefined; - vertex_state_desc.vertexBufferCount = 1; - vertex_state_desc.vertexBuffers = &buffer_binding_desc; - - graphics_pipeline_desc.vertexState = &vertex_state_desc; + graphics_pipeline_desc.vertex.bufferCount = 1; + graphics_pipeline_desc.vertex.buffers = buffer_layouts; // Create the pixel shader WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__glsl_shader_frag_spv, sizeof(__glsl_shader_frag_spv) / sizeof(uint32_t)); - graphics_pipeline_desc.fragmentStage = &pixel_shader_desc; // Create the blending setup - WGPUColorStateDescriptor color_state = {}; - { - color_state.format = g_renderTargetFormat; - color_state.alphaBlend.operation = WGPUBlendOperation_Add; - color_state.alphaBlend.srcFactor = WGPUBlendFactor_SrcAlpha; - color_state.alphaBlend.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; - color_state.colorBlend.operation = WGPUBlendOperation_Add; - color_state.colorBlend.srcFactor = WGPUBlendFactor_SrcAlpha; - color_state.colorBlend.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; - color_state.writeMask = WGPUColorWriteMask_All; - - graphics_pipeline_desc.colorStateCount = 1; - graphics_pipeline_desc.colorStates = &color_state; - graphics_pipeline_desc.alphaToCoverageEnabled = false; - } - - // Create the rasterizer state - WGPURasterizationStateDescriptor raster_desc = {}; - { - raster_desc.cullMode = WGPUCullMode_None; - raster_desc.frontFace = WGPUFrontFace_CW; - raster_desc.depthBias = 0; - raster_desc.depthBiasClamp = 0; - raster_desc.depthBiasSlopeScale = 0; - graphics_pipeline_desc.rasterizationState = &raster_desc; - } + WGPUBlendState blend_state = {}; + blend_state.alpha.operation = WGPUBlendOperation_Add; + blend_state.alpha.srcFactor = WGPUBlendFactor_One; + blend_state.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + blend_state.color.operation = WGPUBlendOperation_Add; + blend_state.color.srcFactor = WGPUBlendFactor_SrcAlpha; + blend_state.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + + WGPUColorTargetState color_state = {}; + color_state.format = g_renderTargetFormat; + color_state.blend = &blend_state; + color_state.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState fragment_state = {}; + fragment_state.module = pixel_shader_desc.module; + fragment_state.entryPoint = pixel_shader_desc.entryPoint; + fragment_state.targetCount = 1; + fragment_state.targets = &color_state; + + graphics_pipeline_desc.fragment = &fragment_state; // Create depth-stencil State - WGPUDepthStencilStateDescriptor depth_desc = {}; - { - // Configure disabled state - depth_desc.format = WGPUTextureFormat_Undefined; - depth_desc.depthWriteEnabled = true; - depth_desc.depthCompare = WGPUCompareFunction_Always; - depth_desc.stencilReadMask = 0; - depth_desc.stencilWriteMask = 0; - depth_desc.stencilBack.compare = WGPUCompareFunction_Always; - depth_desc.stencilBack.failOp = WGPUStencilOperation_Keep; - depth_desc.stencilBack.depthFailOp = WGPUStencilOperation_Keep; - depth_desc.stencilBack.passOp = WGPUStencilOperation_Keep; - depth_desc.stencilFront.compare = WGPUCompareFunction_Always; - depth_desc.stencilFront.failOp = WGPUStencilOperation_Keep; - depth_desc.stencilFront.depthFailOp = WGPUStencilOperation_Keep; - depth_desc.stencilFront.passOp = WGPUStencilOperation_Keep; - - // No depth buffer corresponds to no configuration - graphics_pipeline_desc.depthStencilState = NULL; - } + WGPUDepthStencilState depth_stencil_state = {}; + depth_stencil_state.depthBias = 0; + depth_stencil_state.depthBiasClamp = 0; + depth_stencil_state.depthBiasSlopeScale = 0; - g_pipelineState = wgpuDeviceCreateRenderPipeline(g_wgpuDevice, &graphics_pipeline_desc); + // Configure disabled depth-stencil state + graphics_pipeline_desc.depthStencil = nullptr; + + g_pipelineState = wgpuDeviceCreateRenderPipeline2(g_wgpuDevice, &graphics_pipeline_desc); ImGui_ImplWGPU_CreateFontsTexture(); ImGui_ImplWGPU_CreateUniformBuffer(); // Create resource bind group + WGPUBindGroupLayout bg_layouts[2]; + bg_layouts[0] = wgpuRenderPipelineGetBindGroupLayout(g_pipelineState, 0); + bg_layouts[1] = wgpuRenderPipelineGetBindGroupLayout(g_pipelineState, 1); + WGPUBindGroupEntry common_bg_entries[] = { { 0, g_resources.Uniforms, 0, sizeof(Uniforms), 0, 0 }, @@ -700,10 +631,10 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry); common_bg_descriptor.entries = common_bg_entries; g_resources.CommonBindGroup = wgpuDeviceCreateBindGroup(g_wgpuDevice, &common_bg_descriptor); - g_resources.ImageBindGroupLayout = bg_layouts[1]; WGPUBindGroup image_bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bg_layouts[1], g_resources.FontTextureView); g_resources.ImageBindGroup = image_bind_group; + g_resources.ImageBindGroupLayout = bg_layouts[1]; g_resources.ImageBindGroups.SetVoidPtr(ImHashData(&g_resources.FontTextureView, sizeof(ImTextureID)), image_bind_group); SafeRelease(vertex_shader_desc.module); @@ -730,12 +661,13 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects() bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format) { - // Setup back-end capabilities flags + // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_webgpu"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. g_wgpuDevice = device; + g_defaultQueue = wgpuDeviceGetQueue(g_wgpuDevice); g_renderTargetFormat = rt_format; g_pFrameResources = new FrameResources[num_frames_in_flight]; g_numFramesInFlight = num_frames_in_flight; @@ -746,9 +678,9 @@ bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextur g_resources.Sampler = NULL; g_resources.Uniforms = NULL; g_resources.CommonBindGroup = NULL; - g_resources.ImageBindGroupLayout = NULL; g_resources.ImageBindGroups.Data.reserve(100); g_resources.ImageBindGroup = NULL; + g_resources.ImageBindGroupLayout = NULL; // Create buffers with a default size (they will later be grown as needed) for (int i = 0; i < num_frames_in_flight; i++) @@ -770,6 +702,7 @@ void ImGui_ImplWGPU_Shutdown() ImGui_ImplWGPU_InvalidateDeviceObjects(); delete[] g_pFrameResources; g_pFrameResources = NULL; + wgpuQueueRelease(g_defaultQueue); g_wgpuDevice = NULL; g_numFramesInFlight = 0; g_frameIndex = UINT_MAX; diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.h b/examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.h similarity index 82% rename from examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.h index f6c61528..ec10768e 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_wgpu.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_wgpu.h @@ -6,7 +6,8 @@ // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_win32.cpp b/examples/interactive/imgui-1.83/backends/imgui_impl_win32.cpp similarity index 67% rename from examples/interactive/imgui-1.81/backends/imgui_impl_win32.cpp rename to examples/interactive/imgui-1.83/backends/imgui_impl_win32.cpp index 29b7df45..051a1b7d 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_win32.cpp +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_win32.cpp @@ -8,7 +8,8 @@ // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -19,10 +20,14 @@ #endif #include #include +#include + +// Configuration flags to add in your imconfig.h file: +//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD // Disable gamepad support. This was meaningful before <1.81 but we now load XInput dynamically so the option is now less relevant. // Using XInput for gamepad (will load DLL dynamically) #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD -#include +#include typedef DWORD (WINAPI *PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*); typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); #endif @@ -30,6 +35,12 @@ typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using TrackMouseEvent() to receive WM_MOUSELEAVE events). +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-06-08: Fix ImGui_ImplWin32_EnableDpiAwareness() and ImGui_ImplWin32_GetDpiScaleForMonitor() to handle Windows 8.1/10 features without a manifest (per-monitor DPI, and properly calls SetProcessDpiAwareness() on 8.1). +// 2021-03-23: Inputs: Clearing keyboard down array when losing focus (WM_KILLFOCUS). +// 2021-02-18: Added ImGui_ImplWin32_EnableAlphaCompositing(). Non Visual Studio users will need to link with dwmapi.lib (MinGW/gcc: use -ldwmapi). +// 2021-02-17: Fixed ImGui_ImplWin32_EnableDpiAwareness() attempting to get SetProcessDpiAwareness from shcore.dll on Windows 8 whereas it is only supported on Windows 8.1. // 2021-01-25: Inputs: Dynamically loading XInput DLL. // 2020-12-04: Misc: Fixed setting of io.DisplaySize to invalid/uninitialized data when after hwnd has been closed. // 2020-03-03: Inputs: Calling AddInputCharacterUTF16() to support surrogate pairs leading to codepoint >= 0x10000 (for more complete CJK inputs) @@ -56,51 +67,76 @@ typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); // 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging. // 2016-11-12: Inputs: Only call Win32 ::SetCursor(NULL) when io.MouseDrawCursor is set. -// Win32 Data -static HWND g_hWnd = NULL; -static INT64 g_Time = 0; -static INT64 g_TicksPerSecond = 0; -static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT; -static bool g_HasGamepad = false; -static bool g_WantUpdateHasGamepad = true; -static bool g_WantUpdateMonitors = true; - // Forward Declarations static void ImGui_ImplWin32_InitPlatformInterface(); static void ImGui_ImplWin32_ShutdownPlatformInterface(); static void ImGui_ImplWin32_UpdateMonitors(); -// XInput DLL and functions +struct ImGui_ImplWin32_Data +{ + HWND hWnd; + HWND MouseHwnd; + bool MouseTracked; + INT64 Time; + INT64 TicksPerSecond; + ImGuiMouseCursor LastMouseCursor; + bool HasGamepad; + bool WantUpdateHasGamepad; + bool WantUpdateMonitors; + #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD -static HMODULE g_XInputDLL = NULL; -static PFN_XInputGetCapabilities g_XInputGetCapabilities = NULL; -static PFN_XInputGetState g_XInputGetState = NULL; + HMODULE XInputDLL; + PFN_XInputGetCapabilities XInputGetCapabilities; + PFN_XInputGetState XInputGetState; #endif + ImGui_ImplWin32_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in this backend. +// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. +static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; +} + // Functions bool ImGui_ImplWin32_Init(void* hwnd) { - if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&g_TicksPerSecond)) + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); + + INT64 perf_frequency, perf_counter; + if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&perf_frequency)) return false; - if (!::QueryPerformanceCounter((LARGE_INTEGER*)&g_Time)) + if (!::QueryPerformanceCounter((LARGE_INTEGER*)&perf_counter)) return false; // Setup backend capabilities flags - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = IM_NEW(ImGui_ImplWin32_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_win32"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can set io.MouseHoveredViewport correctly (optional, not easy) - io.BackendPlatformName = "imgui_impl_win32"; + + bd->hWnd = (HWND)hwnd; + bd->WantUpdateHasGamepad = true; + bd->WantUpdateMonitors = true; + bd->TicksPerSecond = perf_frequency; + bd->Time = perf_counter; + bd->LastMouseCursor = ImGuiMouseCursor_COUNT; // Our mouse update function expect PlatformHandle to be filled for the main viewport - g_hWnd = (HWND)hwnd; ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - main_viewport->PlatformHandle = main_viewport->PlatformHandleRaw = (void*)g_hWnd; + main_viewport->PlatformHandle = main_viewport->PlatformHandleRaw = (void*)bd->hWnd; if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplWin32_InitPlatformInterface(); - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_Tab] = VK_TAB; io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; @@ -137,35 +173,31 @@ bool ImGui_ImplWin32_Init(void* hwnd) for (int n = 0; n < IM_ARRAYSIZE(xinput_dll_names); n++) if (HMODULE dll = ::LoadLibraryA(xinput_dll_names[n])) { - g_XInputDLL = dll; - g_XInputGetCapabilities = (PFN_XInputGetCapabilities)::GetProcAddress(dll, "XInputGetCapabilities"); - g_XInputGetState = (PFN_XInputGetState)::GetProcAddress(dll, "XInputGetState"); + bd->XInputDLL = dll; + bd->XInputGetCapabilities = (PFN_XInputGetCapabilities)::GetProcAddress(dll, "XInputGetCapabilities"); + bd->XInputGetState = (PFN_XInputGetState)::GetProcAddress(dll, "XInputGetState"); break; } #endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - + return true; } void ImGui_ImplWin32_Shutdown() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); ImGui_ImplWin32_ShutdownPlatformInterface(); // Unload XInput library #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - if (g_XInputDLL) - ::FreeLibrary(g_XInputDLL); - g_XInputDLL = NULL; - g_XInputGetCapabilities = NULL; - g_XInputGetState = NULL; + if (bd->XInputDLL) + ::FreeLibrary(bd->XInputDLL); #endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - - g_hWnd = NULL; - g_Time = 0; - g_TicksPerSecond = 0; - g_LastMouseCursor = ImGuiMouseCursor_COUNT; - g_HasGamepad = false; - g_WantUpdateHasGamepad = true; + + io.BackendPlatformName = NULL; + io.BackendPlatformUserData = NULL; + IM_DELETE(bd); } static bool ImGui_ImplWin32_UpdateMouseCursor() @@ -205,47 +237,52 @@ static bool ImGui_ImplWin32_UpdateMouseCursor() // Because of that, it is a little more complicated than your typical single-viewport binding code! static void ImGui_ImplWin32_UpdateMousePos() { + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(bd->hWnd != 0); + + const ImVec2 mouse_pos_prev = io.MousePos; + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + io.MouseHoveredViewport = 0; + + // Obtain focused and hovered window. We forward mouse input when focused or when hovered (and no other window is capturing) + HWND focused_window = ::GetForegroundWindow(); + HWND hovered_window = bd->MouseHwnd; + HWND mouse_window = NULL; + if (hovered_window && (hovered_window == bd->hWnd || ::IsChild(hovered_window, bd->hWnd) || ImGui::FindViewportByPlatformHandle((void*)hovered_window))) + mouse_window = hovered_window; + else if (focused_window && (focused_window == bd->hWnd || ::IsChild(focused_window, bd->hWnd) || ImGui::FindViewportByPlatformHandle((void*)focused_window))) + mouse_window = focused_window; + if (mouse_window == NULL) + return; - // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) - // (When multi-viewports are enabled, all imgui positions are same as OS positions) + // Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + // (When multi-viewports are enabled, all Dear ImGui positions are same as OS positions) if (io.WantSetMousePos) { - POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y }; - if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0 || ::ClientToScreen(g_hWnd, &pos)) - ::ClientToScreen(g_hWnd, &pos); + POINT pos = { (int)mouse_pos_prev.x, (int)mouse_pos_prev.y }; + if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) == 0) + ::ClientToScreen(mouse_window, &pos); ::SetCursorPos(pos.x, pos.y); } - io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); - io.MouseHoveredViewport = 0; - - // Set imgui mouse position + // Set Dear ImGui mouse position from OS position POINT mouse_screen_pos; if (!::GetCursorPos(&mouse_screen_pos)) return; - if (HWND focused_hwnd = ::GetForegroundWindow()) + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { - if (::IsChild(focused_hwnd, g_hWnd)) - focused_hwnd = g_hWnd; - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) - // This is the position you can get with GetCursorPos(). In theory adding viewport->Pos is also the reverse operation of doing ScreenToClient(). - if (ImGui::FindViewportByPlatformHandle((void*)focused_hwnd) != NULL) - io.MousePos = ImVec2((float)mouse_screen_pos.x, (float)mouse_screen_pos.y); - } - else - { - // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window.) - // This is the position you can get with GetCursorPos() + ScreenToClient() or from WM_MOUSEMOVE. - if (focused_hwnd == g_hWnd) - { - POINT mouse_client_pos = mouse_screen_pos; - ::ScreenToClient(focused_hwnd, &mouse_client_pos); - io.MousePos = ImVec2((float)mouse_client_pos.x, (float)mouse_client_pos.y); - } - } + // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) + // This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same. + io.MousePos = ImVec2((float)mouse_screen_pos.x, (float)mouse_screen_pos.y); + } + else + { + // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) + // This is the position you can get with ::GetCursorPos() + ::ScreenToClient() or WM_MOUSEMOVE. + POINT mouse_client_pos = mouse_screen_pos; + ::ScreenToClient(bd->hWnd, &mouse_client_pos); + io.MousePos = ImVec2((float)mouse_client_pos.x, (float)mouse_client_pos.y); } // (Optional) When using multiple viewports: set io.MouseHoveredViewport to the viewport the OS mouse cursor is hovering. @@ -265,22 +302,23 @@ static void ImGui_ImplWin32_UpdateGamepads() { #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); memset(io.NavInputs, 0, sizeof(io.NavInputs)); if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) return; // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow. // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE. - if (g_WantUpdateHasGamepad) + if (bd->WantUpdateHasGamepad) { XINPUT_CAPABILITIES caps; - g_HasGamepad = g_XInputGetCapabilities ? (g_XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) : false; - g_WantUpdateHasGamepad = false; + bd->HasGamepad = bd->XInputGetCapabilities ? (bd->XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) : false; + bd->WantUpdateHasGamepad = false; } io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; XINPUT_STATE xinput_state; - if (g_HasGamepad && g_XInputGetState && g_XInputGetState(0, &xinput_state) == ERROR_SUCCESS) + if (bd->HasGamepad && bd->XInputGetState && bd->XInputGetState(0, &xinput_state) == ERROR_SUCCESS) { const XINPUT_GAMEPAD& gamepad = xinput_state.Gamepad; io.BackendFlags |= ImGuiBackendFlags_HasGamepad; @@ -331,28 +369,30 @@ static BOOL CALLBACK ImGui_ImplWin32_UpdateMonitors_EnumFunc(HMONITOR monitor, H static void ImGui_ImplWin32_UpdateMonitors() { + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); ImGui::GetPlatformIO().Monitors.resize(0); - ::EnumDisplayMonitors(NULL, NULL, ImGui_ImplWin32_UpdateMonitors_EnumFunc, NULL); - g_WantUpdateMonitors = false; + ::EnumDisplayMonitors(NULL, NULL, ImGui_ImplWin32_UpdateMonitors_EnumFunc, 0); + bd->WantUpdateMonitors = false; } void ImGui_ImplWin32_NewFrame() { ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplWin32_Init()?"); // Setup display size (every frame to accommodate for window resizing) RECT rect = { 0, 0, 0, 0 }; - ::GetClientRect(g_hWnd, &rect); + ::GetClientRect(bd->hWnd, &rect); io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - if (g_WantUpdateMonitors) + if (bd->WantUpdateMonitors) ImGui_ImplWin32_UpdateMonitors(); // Setup time step INT64 current_time = 0; ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); - io.DeltaTime = (float)(current_time - g_Time) / g_TicksPerSecond; - g_Time = current_time; + io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond; + bd->Time = current_time; // Read keyboard modifiers inputs io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; @@ -366,9 +406,9 @@ void ImGui_ImplWin32_NewFrame() // Update OS mouse cursor with the cursor requested by imgui ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); - if (g_LastMouseCursor != mouse_cursor) + if (bd->LastMouseCursor != mouse_cursor) { - g_LastMouseCursor = mouse_cursor; + bd->LastMouseCursor = mouse_cursor; ImGui_ImplWin32_UpdateMouseCursor(); } @@ -402,8 +442,25 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA return 0; ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + switch (msg) { + case WM_MOUSEMOVE: + // We need to call TrackMouseEvent in order to receive WM_MOUSELEAVE events + bd->MouseHwnd = hwnd; + if (!bd->MouseTracked) + { + TRACKMOUSEEVENT tme = { sizeof(tme), TME_LEAVE, hwnd, 0 }; + ::TrackMouseEvent(&tme); + bd->MouseTracked = true; + } + break; + case WM_MOUSELEAVE: + if (bd->MouseHwnd == hwnd) + bd->MouseHwnd = NULL; + bd->MouseTracked = false; + break; case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: @@ -450,6 +507,9 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA if (wParam < 256) io.KeysDown[wParam] = 0; return 0; + case WM_KILLFOCUS: + memset(io.KeysDown, 0, sizeof(io.KeysDown)); + return 0; case WM_CHAR: // You can also use ToAscii()+GetKeyboardState() to retrieve characters. if (wParam > 0 && wParam < 0x10000) @@ -461,10 +521,10 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA return 0; case WM_DEVICECHANGE: if ((UINT)wParam == DBT_DEVNODES_CHANGED) - g_WantUpdateHasGamepad = true; + bd->WantUpdateHasGamepad = true; return 0; case WM_DISPLAYCHANGE: - g_WantUpdateMonitors = true; + bd->WantUpdateMonitors = true; return 0; } return 0; @@ -485,19 +545,32 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA // If you are trying to implement your own backend for your own engine, you may ignore that noise. //--------------------------------------------------------------------------------------------------------- -// Implement some of the functions and types normally declared in recent Windows SDK. -#if !defined(_versionhelpers_H_INCLUDED_) && !defined(_INC_VERSIONHELPERS) -static BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp) +// Perform our own check with RtlVerifyVersionInfo() instead of using functions from as they +// require a manifest to be functional for checks above 8.1. See https://github.com/ocornut/imgui/issues/4200 +static BOOL _IsWindowsVersionOrGreater(WORD major, WORD minor, WORD) { - OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, { 0 }, sp, 0, 0, 0, 0 }; - DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR; - ULONGLONG cond = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL); - cond = ::VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL); - cond = ::VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - return ::VerifyVersionInfoW(&osvi, mask, cond); + typedef LONG(WINAPI* PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*, ULONG, ULONGLONG); + static PFN_RtlVerifyVersionInfo RtlVerifyVersionInfoFn = NULL; + if (RtlVerifyVersionInfoFn == NULL) + if (HMODULE ntdllModule = ::GetModuleHandleA("ntdll.dll")) + RtlVerifyVersionInfoFn = (PFN_RtlVerifyVersionInfo)GetProcAddress(ntdllModule, "RtlVerifyVersionInfo"); + if (RtlVerifyVersionInfoFn == NULL) + return FALSE; + + RTL_OSVERSIONINFOEXW versionInfo = { }; + ULONGLONG conditionMask = 0; + versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + versionInfo.dwMajorVersion = major; + versionInfo.dwMinorVersion = minor; + VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); + return (RtlVerifyVersionInfoFn(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == 0) ? TRUE : FALSE; } -#define IsWindows8Point1OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WINBLUE -#endif + +#define _IsWindowsVistaOrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0600), LOBYTE(0x0600), 0) // _WIN32_WINNT_VISTA +#define _IsWindows8OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WIN8 +#define _IsWindows8Point1OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0603), LOBYTE(0x0603), 0) // _WIN32_WINNT_WINBLUE +#define _IsWindows10OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0A00), LOBYTE(0x0A00), 0) // _WIN32_WINNT_WINTHRESHOLD / _WIN32_WINNT_WIN10 #ifndef DPI_ENUMS_DECLARED typedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; @@ -518,9 +591,10 @@ typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWAR void ImGui_ImplWin32_EnableDpiAwareness() { // Make sure monitors will be updated with latest correct scaling - g_WantUpdateMonitors = true; + if (ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData()) + bd->WantUpdateMonitors = true; - // if (IsWindows10OrGreater()) // This needs a manifest to succeed. Instead we try to grab the function pointer! + if (_IsWindows10OrGreater()) { static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) @@ -529,7 +603,7 @@ void ImGui_ImplWin32_EnableDpiAwareness() return; } } - if (IsWindows8Point1OrGreater()) + if (_IsWindows8Point1OrGreater()) { static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness")) @@ -544,29 +618,32 @@ void ImGui_ImplWin32_EnableDpiAwareness() } #if defined(_MSC_VER) && !defined(NOGDI) -#pragma comment(lib, "gdi32") // Link with gdi32.lib for GetDeviceCaps() +#pragma comment(lib, "gdi32") // Link with gdi32.lib for GetDeviceCaps(). MinGW will require linking with '-lgdi32' #endif float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) { UINT xdpi = 96, ydpi = 96; - static BOOL bIsWindows8Point1OrGreater = IsWindows8Point1OrGreater(); - if (bIsWindows8Point1OrGreater) + if (_IsWindows8Point1OrGreater()) { - static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process - if (PFN_GetDpiForMonitor GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor")) - GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + static PFN_GetDpiForMonitor GetDpiForMonitorFn = NULL; + if (GetDpiForMonitorFn == NULL && shcore_dll != NULL) + GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor"); + if (GetDpiForMonitorFn != NULL) + { + GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! + return xdpi / 96.0f; + } } #ifndef NOGDI - else - { - const HDC dc = ::GetDC(NULL); - xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); - ydpi = ::GetDeviceCaps(dc, LOGPIXELSY); - ::ReleaseDC(NULL, dc); - } -#endif + const HDC dc = ::GetDC(NULL); + xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); + ydpi = ::GetDeviceCaps(dc, LOGPIXELSY); IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! + ::ReleaseDC(NULL, dc); +#endif return xdpi / 96.0f; } @@ -585,7 +662,7 @@ float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd) #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS #endif -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(__GNUC__) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) #define HAS_WIN32_IME 1 #include #ifdef _MSC_VER @@ -612,15 +689,15 @@ static void ImGui_ImplWin32_SetImeInputPos(ImGuiViewport* viewport, ImVec2 pos) //-------------------------------------------------------------------------------------------------------- // Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. -struct ImGuiViewportDataWin32 +struct ImGui_ImplWin32_ViewportData { HWND Hwnd; bool HwndOwned; DWORD DwStyle; DWORD DwExStyle; - ImGuiViewportDataWin32() { Hwnd = NULL; HwndOwned = false; DwStyle = DwExStyle = 0; } - ~ImGuiViewportDataWin32() { IM_ASSERT(Hwnd == NULL); } + ImGui_ImplWin32_ViewportData() { Hwnd = NULL; HwndOwned = false; DwStyle = DwExStyle = 0; } + ~ImGui_ImplWin32_ViewportData() { IM_ASSERT(Hwnd == NULL); } }; static void ImGui_ImplWin32_GetWin32StyleFromViewportFlags(ImGuiViewportFlags flags, DWORD* out_style, DWORD* out_ex_style) @@ -641,11 +718,11 @@ static void ImGui_ImplWin32_GetWin32StyleFromViewportFlags(ImGuiViewportFlags fl static void ImGui_ImplWin32_CreateWindow(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = IM_NEW(ImGuiViewportDataWin32)(); - viewport->PlatformUserData = data; + ImGui_ImplWin32_ViewportData* vd = IM_NEW(ImGui_ImplWin32_ViewportData)(); + viewport->PlatformUserData = vd; // Select style and parent window - ImGui_ImplWin32_GetWin32StyleFromViewportFlags(viewport->Flags, &data->DwStyle, &data->DwExStyle); + ImGui_ImplWin32_GetWin32StyleFromViewportFlags(viewport->Flags, &vd->DwStyle, &vd->DwExStyle); HWND parent_window = NULL; if (viewport->ParentViewportId != 0) if (ImGuiViewport* parent_viewport = ImGui::FindViewportByID(viewport->ParentViewportId)) @@ -653,169 +730,170 @@ static void ImGui_ImplWin32_CreateWindow(ImGuiViewport* viewport) // Create window RECT rect = { (LONG)viewport->Pos.x, (LONG)viewport->Pos.y, (LONG)(viewport->Pos.x + viewport->Size.x), (LONG)(viewport->Pos.y + viewport->Size.y) }; - ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); - data->Hwnd = ::CreateWindowEx( - data->DwExStyle, _T("ImGui Platform"), _T("Untitled"), data->DwStyle, // Style, class name, window name + ::AdjustWindowRectEx(&rect, vd->DwStyle, FALSE, vd->DwExStyle); + vd->Hwnd = ::CreateWindowEx( + vd->DwExStyle, _T("ImGui Platform"), _T("Untitled"), vd->DwStyle, // Style, class name, window name rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, // Window area parent_window, NULL, ::GetModuleHandle(NULL), NULL); // Parent window, Menu, Instance, Param - data->HwndOwned = true; + vd->HwndOwned = true; viewport->PlatformRequestResize = false; - viewport->PlatformHandle = viewport->PlatformHandleRaw = data->Hwnd; + viewport->PlatformHandle = viewport->PlatformHandleRaw = vd->Hwnd; } static void ImGui_ImplWin32_DestroyWindow(ImGuiViewport* viewport) { - if (ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData) + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + if (ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData) { - if (::GetCapture() == data->Hwnd) + if (::GetCapture() == vd->Hwnd) { // Transfer capture so if we started dragging from a window that later disappears, we'll still receive the MOUSEUP event. ::ReleaseCapture(); - ::SetCapture(g_hWnd); + ::SetCapture(bd->hWnd); } - if (data->Hwnd && data->HwndOwned) - ::DestroyWindow(data->Hwnd); - data->Hwnd = NULL; - IM_DELETE(data); + if (vd->Hwnd && vd->HwndOwned) + ::DestroyWindow(vd->Hwnd); + vd->Hwnd = NULL; + IM_DELETE(vd); } viewport->PlatformUserData = viewport->PlatformHandle = NULL; } static void ImGui_ImplWin32_ShowWindow(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) - ::ShowWindow(data->Hwnd, SW_SHOWNA); + ::ShowWindow(vd->Hwnd, SW_SHOWNA); else - ::ShowWindow(data->Hwnd, SW_SHOW); + ::ShowWindow(vd->Hwnd, SW_SHOW); } static void ImGui_ImplWin32_UpdateWindow(ImGuiViewport* viewport) { // (Optional) Update Win32 style if it changed _after_ creation. // Generally they won't change unless configuration flags are changed, but advanced uses (such as manually rewriting viewport flags) make this useful. - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); DWORD new_style; DWORD new_ex_style; ImGui_ImplWin32_GetWin32StyleFromViewportFlags(viewport->Flags, &new_style, &new_ex_style); // Only reapply the flags that have been changed from our point of view (as other flags are being modified by Windows) - if (data->DwStyle != new_style || data->DwExStyle != new_ex_style) + if (vd->DwStyle != new_style || vd->DwExStyle != new_ex_style) { // (Optional) Update TopMost state if it changed _after_ creation - bool top_most_changed = (data->DwExStyle & WS_EX_TOPMOST) != (new_ex_style & WS_EX_TOPMOST); + bool top_most_changed = (vd->DwExStyle & WS_EX_TOPMOST) != (new_ex_style & WS_EX_TOPMOST); HWND insert_after = top_most_changed ? ((viewport->Flags & ImGuiViewportFlags_TopMost) ? HWND_TOPMOST : HWND_NOTOPMOST) : 0; UINT swp_flag = top_most_changed ? 0 : SWP_NOZORDER; // Apply flags and position (since it is affected by flags) - data->DwStyle = new_style; - data->DwExStyle = new_ex_style; - ::SetWindowLong(data->Hwnd, GWL_STYLE, data->DwStyle); - ::SetWindowLong(data->Hwnd, GWL_EXSTYLE, data->DwExStyle); + vd->DwStyle = new_style; + vd->DwExStyle = new_ex_style; + ::SetWindowLong(vd->Hwnd, GWL_STYLE, vd->DwStyle); + ::SetWindowLong(vd->Hwnd, GWL_EXSTYLE, vd->DwExStyle); RECT rect = { (LONG)viewport->Pos.x, (LONG)viewport->Pos.y, (LONG)(viewport->Pos.x + viewport->Size.x), (LONG)(viewport->Pos.y + viewport->Size.y) }; - ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); // Client to Screen - ::SetWindowPos(data->Hwnd, insert_after, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, swp_flag | SWP_NOACTIVATE | SWP_FRAMECHANGED); - ::ShowWindow(data->Hwnd, SW_SHOWNA); // This is necessary when we alter the style + ::AdjustWindowRectEx(&rect, vd->DwStyle, FALSE, vd->DwExStyle); // Client to Screen + ::SetWindowPos(vd->Hwnd, insert_after, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, swp_flag | SWP_NOACTIVATE | SWP_FRAMECHANGED); + ::ShowWindow(vd->Hwnd, SW_SHOWNA); // This is necessary when we alter the style viewport->PlatformRequestMove = viewport->PlatformRequestResize = true; } } static ImVec2 ImGui_ImplWin32_GetWindowPos(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); POINT pos = { 0, 0 }; - ::ClientToScreen(data->Hwnd, &pos); + ::ClientToScreen(vd->Hwnd, &pos); return ImVec2((float)pos.x, (float)pos.y); } static void ImGui_ImplWin32_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); RECT rect = { (LONG)pos.x, (LONG)pos.y, (LONG)pos.x, (LONG)pos.y }; - ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); - ::SetWindowPos(data->Hwnd, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + ::AdjustWindowRectEx(&rect, vd->DwStyle, FALSE, vd->DwExStyle); + ::SetWindowPos(vd->Hwnd, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } static ImVec2 ImGui_ImplWin32_GetWindowSize(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); RECT rect; - ::GetClientRect(data->Hwnd, &rect); + ::GetClientRect(vd->Hwnd, &rect); return ImVec2(float(rect.right - rect.left), float(rect.bottom - rect.top)); } static void ImGui_ImplWin32_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); RECT rect = { 0, 0, (LONG)size.x, (LONG)size.y }; - ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); // Client to Screen - ::SetWindowPos(data->Hwnd, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + ::AdjustWindowRectEx(&rect, vd->DwStyle, FALSE, vd->DwExStyle); // Client to Screen + ::SetWindowPos(vd->Hwnd, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); } static void ImGui_ImplWin32_SetWindowFocus(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); - ::BringWindowToTop(data->Hwnd); - ::SetForegroundWindow(data->Hwnd); - ::SetFocus(data->Hwnd); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); + ::BringWindowToTop(vd->Hwnd); + ::SetForegroundWindow(vd->Hwnd); + ::SetFocus(vd->Hwnd); } static bool ImGui_ImplWin32_GetWindowFocus(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); - return ::GetForegroundWindow() == data->Hwnd; + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); + return ::GetForegroundWindow() == vd->Hwnd; } static bool ImGui_ImplWin32_GetWindowMinimized(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); - return ::IsIconic(data->Hwnd) != 0; + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); + return ::IsIconic(vd->Hwnd) != 0; } static void ImGui_ImplWin32_SetWindowTitle(ImGuiViewport* viewport, const char* title) { // ::SetWindowTextA() doesn't properly handle UTF-8 so we explicitely convert our string. - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); int n = ::MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); ImVector title_w; title_w.resize(n); ::MultiByteToWideChar(CP_UTF8, 0, title, -1, title_w.Data, n); - ::SetWindowTextW(data->Hwnd, title_w.Data); + ::SetWindowTextW(vd->Hwnd, title_w.Data); } static void ImGui_ImplWin32_SetWindowAlpha(ImGuiViewport* viewport, float alpha) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); if (alpha < 1.0f) { - DWORD style = ::GetWindowLongW(data->Hwnd, GWL_EXSTYLE) | WS_EX_LAYERED; - ::SetWindowLongW(data->Hwnd, GWL_EXSTYLE, style); - ::SetLayeredWindowAttributes(data->Hwnd, 0, (BYTE)(255 * alpha), LWA_ALPHA); + DWORD style = ::GetWindowLongW(vd->Hwnd, GWL_EXSTYLE) | WS_EX_LAYERED; + ::SetWindowLongW(vd->Hwnd, GWL_EXSTYLE, style); + ::SetLayeredWindowAttributes(vd->Hwnd, 0, (BYTE)(255 * alpha), LWA_ALPHA); } else { - DWORD style = ::GetWindowLongW(data->Hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED; - ::SetWindowLongW(data->Hwnd, GWL_EXSTYLE, style); + DWORD style = ::GetWindowLongW(vd->Hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED; + ::SetWindowLongW(vd->Hwnd, GWL_EXSTYLE, style); } } static float ImGui_ImplWin32_GetWindowDpiScale(ImGuiViewport* viewport) { - ImGuiViewportDataWin32* data = (ImGuiViewportDataWin32*)viewport->PlatformUserData; - IM_ASSERT(data->Hwnd != 0); - return ImGui_ImplWin32_GetDpiScaleForHwnd(data->Hwnd); + ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; + IM_ASSERT(vd->Hwnd != 0); + return ImGui_ImplWin32_GetDpiScaleForHwnd(vd->Hwnd); } // FIXME-DPI: Testing DPI related ideas @@ -913,11 +991,12 @@ static void ImGui_ImplWin32_InitPlatformInterface() // Register main window handle (which is owned by the main application, not by us) // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - ImGuiViewportDataWin32* data = IM_NEW(ImGuiViewportDataWin32)(); - data->Hwnd = g_hWnd; - data->HwndOwned = false; - main_viewport->PlatformUserData = data; - main_viewport->PlatformHandle = (void*)g_hWnd; + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + ImGui_ImplWin32_ViewportData* vd = IM_NEW(ImGui_ImplWin32_ViewportData)(); + vd->Hwnd = bd->hWnd; + vd->HwndOwned = false; + main_viewport->PlatformUserData = vd; + main_viewport->PlatformHandle = (void*)bd->hWnd; } static void ImGui_ImplWin32_ShutdownPlatformInterface() @@ -926,3 +1005,43 @@ static void ImGui_ImplWin32_ShutdownPlatformInterface() } //--------------------------------------------------------------------------------------------------------- +// Transparency related helpers (optional) +//-------------------------------------------------------------------------------------------------------- + +#if defined(_MSC_VER) +#pragma comment(lib, "dwmapi") // Link with dwmapi.lib. MinGW will require linking with '-ldwmapi' +#endif + +// [experimental] +// Borrowed from GLFW's function updateFramebufferTransparency() in src/win32_window.c +// (the Dwm* functions are Vista era functions but we are borrowing logic from GLFW) +void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd) +{ + if (!_IsWindowsVistaOrGreater()) + return; + + BOOL composition; + if (FAILED(::DwmIsCompositionEnabled(&composition)) || !composition) + return; + + BOOL opaque; + DWORD color; + if (_IsWindows8OrGreater() || (SUCCEEDED(::DwmGetColorizationColor(&color, &opaque)) && !opaque)) + { + HRGN region = ::CreateRectRgn(0, 0, -1, -1); + DWM_BLURBEHIND bb = {}; + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = region; + bb.fEnable = TRUE; + ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb); + ::DeleteObject(region); + } + else + { + DWM_BLURBEHIND bb = {}; + bb.dwFlags = DWM_BB_ENABLE; + ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb); + } +} + +//--------------------------------------------------------------------------------------------------------- diff --git a/examples/interactive/imgui-1.81/backends/imgui_impl_win32.h b/examples/interactive/imgui-1.83/backends/imgui_impl_win32.h similarity index 81% rename from examples/interactive/imgui-1.81/backends/imgui_impl_win32.h rename to examples/interactive/imgui-1.83/backends/imgui_impl_win32.h index e9126cb9..7a9437a6 100644 --- a/examples/interactive/imgui-1.81/backends/imgui_impl_win32.h +++ b/examples/interactive/imgui-1.83/backends/imgui_impl_win32.h @@ -8,7 +8,8 @@ // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs @@ -19,10 +20,6 @@ IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); -// Configuration -// - Disable gamepad support -//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - // Win32 message handler your application need to call. // - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. // - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. @@ -39,3 +36,8 @@ extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor + +// Transparency related helpers (optional) [experimental] +// - Use to enable alpha compositing transparency with the desktop. +// - Use together with e.g. clearing your framebuffer with zero-alpha. +IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd); // HWND hwnd diff --git a/examples/interactive/imgui-1.81/backends/vulkan/generate_spv.sh b/examples/interactive/imgui-1.83/backends/vulkan/generate_spv.sh similarity index 100% rename from examples/interactive/imgui-1.81/backends/vulkan/generate_spv.sh rename to examples/interactive/imgui-1.83/backends/vulkan/generate_spv.sh diff --git a/examples/interactive/imgui-1.81/backends/vulkan/glsl_shader.frag b/examples/interactive/imgui-1.83/backends/vulkan/glsl_shader.frag similarity index 100% rename from examples/interactive/imgui-1.81/backends/vulkan/glsl_shader.frag rename to examples/interactive/imgui-1.83/backends/vulkan/glsl_shader.frag diff --git a/examples/interactive/imgui-1.81/backends/vulkan/glsl_shader.vert b/examples/interactive/imgui-1.83/backends/vulkan/glsl_shader.vert similarity index 100% rename from examples/interactive/imgui-1.81/backends/vulkan/glsl_shader.vert rename to examples/interactive/imgui-1.83/backends/vulkan/glsl_shader.vert diff --git a/examples/interactive/imgui-1.81/docs/BACKENDS.md b/examples/interactive/imgui-1.83/docs/BACKENDS.md similarity index 88% rename from examples/interactive/imgui-1.81/docs/BACKENDS.md rename to examples/interactive/imgui-1.83/docs/BACKENDS.md index 7a723cc7..2ad719a9 100644 --- a/examples/interactive/imgui-1.81/docs/BACKENDS.md +++ b/examples/interactive/imgui-1.83/docs/BACKENDS.md @@ -9,13 +9,15 @@ your application or engine to easily integrate Dear ImGui.** Each backend is typ e.g. Windows ([imgui_impl_win32.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_win32.cpp)), GLFW ([imgui_impl_glfw.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_glfw.cpp)), SDL2 ([imgui_impl_sdl.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdl.cpp)), etc. - The 'Renderer' backends are in charge of: creating atlas texture, rendering imgui draw data.
- e.g. DirectX11 ([imgui_impl_dx11.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_dx11.cpp)), OpenGL/WebGL ([imgui_impl_opengl3.cpp]((https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp)), Vulkan ([imgui_impl_vulkan.cpp]((https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_vulkan.cpp)), etc. + e.g. DirectX11 ([imgui_impl_dx11.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_dx11.cpp)), OpenGL/WebGL ([imgui_impl_opengl3.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp), Vulkan ([imgui_impl_vulkan.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_vulkan.cpp), etc. - For some high-level frameworks, a single backend usually handle both 'Platform' and 'Renderer' parts.
e.g. Allegro 5 ([imgui_impl_allegro5.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_allegro5.cpp)), Marmalade ([imgui_impl_marmalade.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_marmalade.cpp)). If you end up creating a custom backend for your engine, you may want to do the same. An application usually combines 1 Platform backend + 1 Renderer backend + main Dear ImGui sources. -For example, the [example_win32_directx11](https://github.com/ocornut/imgui/tree/master/examples/example_win32_directx11) application combines imgui_impl_win32.cpp + imgui_impl_dx11.cpp. See [EXAMPLES.MD](https://github.com/ocornut/imgui/blob/master/docs/EXAMPLES.md) for details. +For example, the [example_win32_directx11](https://github.com/ocornut/imgui/tree/master/examples/example_win32_directx11) application combines imgui_impl_win32.cpp + imgui_impl_dx11.cpp. There are 20+ examples in the [examples/](https://github.com/ocornut/imgui/blob/master/examples/) folder. See [EXAMPLES.MD](https://github.com/ocornut/imgui/blob/master/docs/EXAMPLES.md) for details. + +**Once Dear ImGui is setup and running, run and refer to `ImGui::ShowDemoWindow()` in imgui_demo.cpp for usage of the end-user API.** ### What are backends @@ -36,7 +38,7 @@ Dear ImGui is highly portable and only requires a few things to run and render, - Optional: multi-viewports support. etc. -This is essentially what each backends are doing + obligatory portability cruft. +This is essentially what each backends are doing + obligatory portability cruft. Using default backends ensure you can get all those features including the ones that would be harder to implement on your side (e.g. multi-viewports support). It is important to understand the difference between the core Dear ImGui library (files in the root folder) and backends which we are describing here (backends/ folder). @@ -46,12 +48,18 @@ and backends which we are describing here (backends/ folder). e.g. you can get creative and use software rendering or render remotely on a different machine. +### Integrating a backend + +See "Getting Started" section of [EXAMPLES.MD](https://github.com/ocornut/imgui/blob/master/docs/EXAMPLES.md) for more details. + + ### List of backends In the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder: List of Platforms Backends: + imgui_impl_android.cpp ; Android native app API imgui_impl_glfw.cpp ; GLFW (Windows, macOS, Linux, etc.) http://www.glfw.org/ imgui_impl_osx.mm ; macOS native API (not as feature complete as glfw/sdl backends) imgui_impl_sdl.cpp ; SDL2 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org diff --git a/examples/interactive/imgui-1.81/docs/CHANGELOG.txt b/examples/interactive/imgui-1.83/docs/CHANGELOG.txt similarity index 87% rename from examples/interactive/imgui-1.81/docs/CHANGELOG.txt rename to examples/interactive/imgui-1.83/docs/CHANGELOG.txt index eab9da23..c610748b 100644 --- a/examples/interactive/imgui-1.81/docs/CHANGELOG.txt +++ b/examples/interactive/imgui-1.83/docs/CHANGELOG.txt @@ -5,11 +5,11 @@ This document holds the user-facing changelog that we also use in release notes. We generally fold multiple commits pertaining to the same topic as a single entry. Changes to backends are also included within the individual .cpp files of each backend. -RELEASE NOTES: https://github.com/ocornut/imgui/releases -REPORT ISSUES, ASK QUESTIONS: https://github.com/ocornut/imgui/issues -COMMITS HISTORY: https://github.com/ocornut/imgui/commits/master -FAQ https://www.dearimgui.org/faq/ -WIKI https://github.com/ocornut/imgui/wiki +RELEASE NOTES: https://github.com/ocornut/imgui/releases +REPORT ISSUES: https://github.com/ocornut/imgui/issues +DISCUSS, ASK QUESTIONS: https://github.com/ocornut/imgui/discussions +FAQ https://www.dearimgui.org/faq/ +WIKI https://github.com/ocornut/imgui/wiki WHEN TO UPDATE? @@ -36,6 +36,7 @@ HOW TO UPDATE? ----------------------------------------------------------------------- DOCKING FEATURES +(see https://github.com/ocornut/imgui/wiki/Docking for quick intro) - Added Docking system: [BETA] (#2109, #351) - Added ImGuiConfigFlags_DockingEnable flag to enable Docking. @@ -54,7 +55,8 @@ DOCKING FEATURES - Style: Added ImGuiCol_DockingPreview, ImGuiCol_DockingEmptyBg colors. - Demo: Added "DockSpace" example app showcasing use of explicit dockspace nodes. -MULTI-VIEWPORT FEATURES (was previously 'viewport' branch, merged into 'docking') +MULTI-VIEWPORT FEATURES +(see https://github.com/ocornut/imgui/wiki/Multi-Viewports for quick intro) Breaking Changes: @@ -65,10 +67,6 @@ Breaking Changes: - Likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them. e.g. subtract GetWindowViewport()->Pos. -- Render function: the ImDrawData structure now contains 'DisplayPos' and 'DisplaySize' fields. - To support multi-viewport, you need to use those values when creating your orthographic projection matrix. - Use 'draw_data->DisplaySize' instead of 'io.DisplaySize', and 'draw_data->DisplayPos' instead of (0,0) as the upper-left point. - You need to subtract 'draw_data->DisplayPos' from your scissor rectangles to convert them from global coordinates to frame-buffer coordinates. - IO: Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. - IO: Removed io.DisplayVisibleMin, io.DisplayVisibleMax settings (they were marked obsoleted, used to clip within the (0,0)..(DisplaySize) range). @@ -81,32 +79,339 @@ Other changes: - ImGuiPlatformIO::Monitors is a list of platform monitors (input from backend) - ImGuiPlatformIO::Viewports is a list of viewports (output from dear imgui) - Added ImGuiPlatformMonitor to feed OS monitor information in the ImGuiPlatformIO::Monitors. -- Added GetMainViewport(). - Added GetWindowViewport(), SetNextWindowViewport(). - Added GetWindowDpiScale(). - Added GetOverlayDrawList(ImGuiViewport* viewport). The no-parameter version of GetOverlayDrawList() return the overlay for the current window's viewport. -- Added UpdatePlatformWindows(), RenderPlatformWindows(), DestroyPlatformWindows() for usage for application core. +- Added UpdatePlatformWindows(), RenderPlatformWindowsDefault(), DestroyPlatformWindows() for usage in application setup. - Added FindViewportByID(), FindViewportByPlatformHandle() for usage by backends. - Added ImGuiConfigFlags_ViewportsEnable configuration flag and other viewport options. -- Added io.ConfigViewportsNoAutoMerge, io.ConfigViewportsNoTaskBarIcon, io.ConfigViewportsNoDecoration, io.ConfigViewportsNoDefaultParent options. +- Added io.ConfigViewportsNoAutoMerge option. +- Added io.ConfigViewportsNoTaskBarIcon option. +- Added io.ConfigViewportsNoDecoration option. +- Added io.ConfigViewportsNoDefaultParent option. - Added ImGuiBackendFlags_PlatformHasViewports, ImGuiBackendFlags_RendererHasViewports, ImGuiBackendFlags_HasMouseHoveredViewport backend flags. - Added io.MouseHoveredViewport (optional _even_ for multi-viewport support, tied to ImGuiBackendFlags_HasMouseHoveredViewport flag). -- Added ImGuiViewport structure, ImGuiViewportFlags flags. +- Expanded ImGuiViewport structure, ImGuiViewportFlags flags. - Added ImGuiWindowClass and SetNextWindowClass() for passing viewport related hints to the OS/platform back-end. -- Examples: Renderer: OpenGL2, OpenGL3, DirectX11, DirectX12, Vulkan: Added support for multi-viewports. +- Examples: Renderer: OpenGL2, OpenGL3, DirectX9, DirectX10, DirectX11, DirectX12, Vulkan: Added support for multi-viewports. - Examples: Platforms: Win32, GLFW, SDL2: Added support for multi-viewports. Note that Linux/Mac still have inconsistent support for multi-viewports. If you want to help see https://github.com/ocornut/imgui/issues/2117. -- Examples: Win32: Added DPI-related helpers to access DPI features without requiring the latest Windows SDK at compile time, - and without requiring Windows 10 at runtime. -- Examples: Vulkan: Added various optional helpers in imgui_impl_vulkan.h (they are used for multi-viewport support) - to make the examples main.cpp easier to read. + + +----------------------------------------------------------------------- + VERSION 1.84 WIP (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +- Commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019): + - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList() + - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder +- Backends: GLFW: backend now needs to use glfwSetCursorEnterCallback(). (#3751, #4377, #2445) + - If calling ImGui_ImplGlfw_InitXXX with install_callbacks=true: this is already done for you. + - If calling ImGui_ImplGlfw_InitXXX with install_callbacks=false: you WILL NEED to register the GLFW callback + with glfwSetCursorEnterCallback(), and forward events to the backend via ImGui_ImplGlfw_CursorEnterCallback(). +- Backends: SDL2: removed unnecessary SDL_Window* parameter from ImGui_ImplSDL2_NewFrame(). (#3244) [@funchal] + Kept inline redirection function (will obsolete). +- Backends: SDL2: backend needs to set 'SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")' in order to + receive mouse clicks events on window focus, otherwise SDL doesn't emit the event. (#3751, #4377, #2445) + This is unfortunately a global SDL setting, so enabling it _might_ have a side-effect on your application. + It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click: + you can ignore SDL_MOUSEBUTTONDOWN that events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED event). +- Internals: (for custom widgets): because disabled items now sets HoveredId, if you want custom widgets to + not react as hovered when disabled, in the majority of use cases it is preferable to check the "hovered" + return value of ButtonBehavior() rather than (HoveredId == id). + +Other Changes: +- Windows: ImGuiWindowFlags_UnsavedDocument/ImGuiTabItmeFlags_UnsavedDocument display a dot instead of a '*' so it + is independent from font style. When in a tab, the dot is displayed at the same position as the close button. + Added extra comments to clarify the purpose of this flag in the context of docked windows. +- Tables: Added ImGuiTableColumnFlags_Disabled acting a master disable over (hidden from user/context menu). (#3935) +- Tables: Clarified that TableSetColumnEnabled() requires the table to use the ImGuiTableFlags_Hideable flag, + because it manipulates the user-accessible show/hide state. (#3935) +- Tables: Added ImGuiTableColumnFlags_NoHeaderLabel to request TableHeadersRow() to not submit label for a column. + Convenient for some small columns. Name will still appear in context menu. (#4206). +- Tables: Fix columns order on TableSetupScrollFreeze() if previous data got frozen columns out of their section. +- Tables: Fix invalid data in TableGetSortSpecs() when SpecsDirty flag is unset. (#4233) +- TabBar: Fixed using more than 32 KB-worth of tab names. (#4176) +- InputInt/InputFloat: When used with Steps values and _ReadOnly flag, the step button look disabled. (#211) +- Drag and Drop: drop target highlight doesn't try to bypass host clipping rectangle. (#4281, #3272) +- Menus: MenuItem() and BeginMenu() are not affected/overlapping when style.SelectableTextAlign is altered. +- Menus: fix hovering a disabled menu or menu item not closing other menus. (#211) +- Popups: fix BeginPopup/OpenPopup sequence failing when there are no focused windows. (#4308) [@rokups] +- Nav: Disabled items are not candidate for default focus. (#211, #787) +- Disabled: disabled items set HoveredId, allowing e.g. HoveredIdTimer to function. (#211, #3419) [@rokups] +- Disabled: disabled mode more consistently release active id if the active item got disabled. (#211) +- Disabled: disabled mode doesn't prevent Selectable() from looking selected. (#211) +- Disabled: fixed IsItemHovered() returning true on disabled item when navigated to. (#211) +- Disabled: fixed IsItemHovered() if popped disabled state after item, or when using Selectable_Disabled. (#211) +- Fixed printf-style format checks on non-MinGW flavors. (#4183, #3592) +- Fonts: Functions with a 'float size_pixels' parameter can accept zero if it is set in ImFontSize::SizePixels. +- Fonts: Prefer using U+FFFD character for fallback instead of '?', if available. (#4269) +- Fonts: Use U+FF0E dot character to construct an ellipsis if U+002E '.' is not available. (#4269) +- Fonts: Add U+FFFD ("replacement character") to default asian glyphs ranges. (#4269) +- Fonts: Fix calling ClearTexData() (clearing CPU side font data) triggering an assert in NewFrame(). (#3487) +- Demo: Fixed requirement in 1.83 to link with imgui_demo.cpp if IMGUI_DISABLE_METRICS_WINDOW is not set. (#4171) + Normally the right way to disable compiling the demo is to set IMGUI_DISABLE_DEMO_WINDOWS, but we want to avoid + implying that the file is required. +- Backends: Reorganized most backends (Win32, SDL, GLFW, OpenGL2/3, DX9/10/11/12, Vulkan, Allegro) to pull their + data from a single structure stored inside the main Dear ImGui context. This facilitate/allow usage of standard + backends with multiple-contexts BUT is only partially tested and not well supported. It is generally advised to + instead use the multi-viewports feature of docking branch where a single Dear ImGui context can be used accross + multiple windows. (#586, #1851, #2004, #3012, #3934, #4141) +- Backends: Win32: Rework to handle certains Windows 8.1/10 features without a manifest. (#4200, #4191) + - ImGui_ImplWin32_GetDpiScaleForMonitor() will handle per-monitor DPI on Windows 10 without a manifest. + - ImGui_ImplWin32_EnableDpiAwareness() will call SetProcessDpiAwareness() fallback on Windows 8.1 without a manifest. +- Backends: Win32: IME functions are disabled by default for non-Visual Studio compilers (MinGW etc.). Enable with + '#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS' for those compilers. Undo change from 1.82. (#2590, #738, #4185, #4301) +- Backends: Win32: Mouse position is correctly reported when the host window is hovered but not focused. (#2445, #2696, #3751, #4377) +- Backends: GLFW: Mouse position is correctly reported when the host window is hovered but not focused. (#3751, #4377, #2445) + (backend now uses glfwSetCursorEnterCallback(). If you called ImGui_ImplGlfw_InitXXX with install_callbacks=false, you will + need to install this callback and forward the data to the backend via ImGui_ImplGlfw_CursorEnterCallback). +- Backends: SDL2: Mouse position is correctly reported when the host window is hovered but not focused. (#3751, #4377, #2445) + (requires SDL 2.0.5+ as SDL_GetMouseFocus() is only usable with SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH). +- Backends: DX9: Explicitly disable texture state stages after >= 1. (#4268) [@NZJenkins] +- Backends: DX12: Fix texture casting crash on 32-bit systems (introduced on 2021/05/19 and v1.83) + added comments + about building on 32-bit systems. (#4225) [@kingofthebongo2008] +- Backends: OpenGL3: Handle GL_CLIP_ORIGIN on <4.5 contexts if "GL_ARB_clip_control" extension is detected. (#4170, #3998) +- Backends: OpenGL3: Destroy vertex/fragment shader objects right after they are linked into main shader. (#4244) [@Crowbarous] +- Backends: OpenGL3: Use OES_vertex_array extension on Emscripten + backup/restore current state. (#4266, #4267) [@harry75369] +- Backends: GLFW: Installing and exposed ImGui_ImplGlfw_MonitorCallback() for forward compatibility with docking branch. +- Backends: OSX: Added a fix for shortcuts using CTRL key instead of CMD key. (#4253) [@rokups] +- Examples: DX12: Fixed handling of Alt+Enter in example app (using swapchain's ResizeBuffers). (#4346) [@PathogenDavid] +- Examples: DX12: Removed unecessary recreation of backend-owned device objects when window is resized. (#4347) [@PathogenDavid] +- Examples: OSX+OpenGL2: Fix event forwarding (fix key remaining stuck when using shortcuts with Cmd/Super key). + Other OSX examples were not affected. (#4253, #1873) [@rokups] +- Examples: Updated all .vcxproj to VS2015 (toolset v140) to facilitate usage with vcpkg. +- Examples: SDL2: Accomodate for vcpkg install having headers in SDL2/SDL.h vs SDL.h. + +Docking+Viewports Branch: + +- Docking: Clicking on the right-most close button of a docking node closes all windows. (#4186) +- Docking: Fix IsWindowAppearing() and ImGuiCond_Appearing on docked windows. (#4177, #3982, #1497, #1061) +- Docking: Fix crash using DockBuilderRemoveNode() in some situations. (#3111, #3179, #3203, #4295) [@hsimyu] +- Docking: Fix crash when a dock node gets re-qualified as dockspace>floating>dockspace, which tends to happen + when incorrectly calling DockBuilderAddNode() without ImGuiDockNodeFlags_Dockspace and using it as a Dockspace + on the next frame after the floating window hosting the node has been automatically created. (#3203, #4295) +- Docking: Reworked node flags saving/inheritance so that flags enforced by docked windows via the + DockNodeFlagsOverrideSet mechanism are are not left in empty dockspace nodes once the windows gets undocked. + (#4292, #3834, #3633, #3521, #3492, #3335, #2999, #2648) +- Docking: (Internal/Experimental) Removed DockNodeFlagsOverrideClear flags from ImGuiWindowClass as + it is ambiguous how to apply them and we haven't got a use out of them yet. +- Viewports: Fix popup/tooltip created without a parent window from being given a ParentViewportId value + from the implicit/fallback window. (#4236, #2409) +- Backends: Vulkan: Fix the use of the incorrect fence for secondary viewports. (#4208) [@FunMiles] + + +----------------------------------------------------------------------- + VERSION 1.83 (Released 2021-05-24) +----------------------------------------------------------------------- + +Decorated log: https://github.com/ocornut/imgui/releases/tag/v1.83 + +Breaking Changes: + +- Backends: Obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID(). (#3761) [@thedmd] + - If you are using official backends from the source tree: you have nothing to do. + - If you copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID(). + Why are we doing this? + - This change will be required in the future when adding support for incremental texture atlas updates. + - Please note this won't break soon, but we are making the change ahead of time. + +Other Changes: + +- Scrolling: Fix scroll tracking with e.g. SetScrollHereX/Y() when WindowPadding < ItemSpacing. +- Scrolling: Fix scroll snapping on edge of scroll region when both scrollbars are enabled. +- Scrolling: Fix mouse wheel axis swap when using SHIFT on macOS (system already does it). (#4010) +- Window: Fix IsWindowAppearing() from returning true twice in most cases. (#3982, #1497, #1061) +- Nav: Fixed toggling menu layer while an InputText() is active not stealing active id. (#787) +- Nav: Fixed pressing Escape to leave menu layer while in a popup or child window. (#787) +- Nav, InputText: Fixed accidental menu toggling while typing non-ascii characters using AltGR. [@rokups] (#370) +- Nav: Fixed using SetItemDefaultFocus() on windows with _NavFlattened flag. (#787) +- Nav: Fixed Tabbing initial activation from skipping the first item if it is tabbable through. (#787) +- Nav: Fixed fast CTRL+Tab (where keys are only held for one single frame) from properly enabling the + menu layer of target window if it doesn't have other active layers. +- Tables: Expose TableSetColumnEnabled() in public api. (#3935) +- Tables: Better preserve widths when columns count changes. (#4046) +- Tables: Sharing more memory buffers between tables, reducing general memory footprints. (#3740) +- TabBar: Fixed mouse reordering with very fast movements (e.g. crossing multiple tabs in a single + frame and then immediately standing still (would only affect automation/bots). [@rokups] +- Menus: made MenuItem() in a menu bar reflect the 'selected' argument with a highlight. (#4128) [@mattelegende] +- Drags, Sliders, Inputs: Specifying a NULL format to Float functions default them to "%.3f" to be + consistent with the compile-time default. (#3922) +- DragScalar: Add default value for v_speed argument to match higher-level functions. (#3922) [@eliasdaler] +- ColorEdit4: Alpha default to 255 (instead of 0) when omitted in hex input. (#3973) [@squadack] +- InputText: Fix handling of paste failure (buffer full) which in some cases could corrupt the undo stack. (#4038) + (fix submitted to https://github.com/nothings/stb/pull/1158) [@Unit2Ed, @ocornut] +- InputText: Do not filter private unicode codepoints (e.g. icons) when pasted from clipboard. (#4005) [@dougbinks] +- InputText: Align caret/cursor to pixel coordinates. (#4080) [@elvissteinjr] +- InputText: Fixed CTRL+Arrow or OSX double-click leaking the presence of spaces when ImGuiInputTextFlags_Password + is used. (#4155, #4156) [@michael-swan] +- LabelText: Fixed clipping of multi-line value text when label is single-line. (#4004) +- LabelText: Fixed vertical alignment of single-line value text when label is multi-line. (#4004) +- Combos: Changed the combo popup to use a different id to also using a context menu with the default item id. + Fixed using BeginPopupContextItem() with no parameter after a combo. (#4167) +- Popups: Added 'OpenPopup(ImGuiID id)' overload to facilitate calling from nested stacks. (#3993, #331) [@zlash] +- Tweak computation of io.Framerate so it is less biased toward high-values in the first 120 frames. (#4138) +- Optimization: Disabling some of MSVC most aggressive Debug runtime checks for some simple/low-level functions + (e.g. ImVec2, ImVector) leading to a 10-20% increase of performances with MSVC "default" Debug settings. +- ImDrawList: Add and use SSE-enabled ImRsqrt() in place of 1.0f / ImSqrt(). (#4091) [@wolfpld] +- ImDrawList: Fixed/improved thickness of thick strokes with sharp angles. (#4053, #3366, #2964, #2868, #2518, #2183) + Effectively introduced a regression in 1.67 (Jan 2019), and a fix in 1.70 (Apr 2019) but the fix wasn't actually on + par with original version. Now incorporating the correct revert. +- ImDrawList: Fixed PathArcTo() regression from 1.82 preventing use of counter-clockwise angles. (#4030, #3491) [@thedmd] +- Demo: Improved popups demo and comments. +- Metrics: Added "Fonts" section with same information as available in "Style Editor">"Fonts". +- Backends: SDL2: Rework global mouse pos availability check listing supported platforms explicitly, + effectively fixing mouse access on Raspberry Pi. (#2837, #3950) [@lethal-guitar, @hinxx] +- Backends: Win32: Clearing keyboard down array when losing focus (WM_KILLFOCUS). (#2062, #3532, #3961) + [@1025798851] +- Backends: OSX: Fix keys remaining stuck when CMD-tabbing to a different application. (#3832) [@rokups] +- Backends: DirectX9: calling IDirect3DStateBlock9::Capture() after CreateStateBlock() which appears to + workaround/fix state restoring issues. Unknown exactly why so, bit of a cargo-cult fix. (#3857) +- Backends: DirectX9: explicitly setting up more graphics states to increase compatibility with unusual + non-default states. (#4063) +- Backends: DirectX10, DirectX11: fixed a crash when backing/restoring state if nothing is bound when + entering the rendering function. (#4045) [@Nemirtingas] +- Backends: GLFW: Adding bound check in KeyCallback because GLFW appears to send -1 on some setups. [#4124] +- Backends: Vulkan: Fix mapped memory Vulkan validation error when buffer sizes are not multiple of + VkPhysicalDeviceLimits::nonCoherentAtomSize. (#3957) [@AgentX1994] +- Backends: WebGPU: Update to latest specs (Chrome Canary 92 and Emscripten 2.0.20). (#4116, #3632) [@bfierz, @Kangz] +- Backends: OpenGL3: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5. (#3998, #2366, #2186) [@s7jones] +- Examples: OpenGL: Add OpenGL ES 2.0 support to modern GL examples. (#2837, #3951) [@lethal-guitar, @hinxx] +- Examples: Vulkan: Rebuild swapchain on VK_SUBOPTIMAL_KHR. (#3881) +- Examples: Vulkan: Prefer using discrete GPU if there are more than one available. (#4012) [@rokups] +- Examples: SDL2: Link with shell32.lib required by SDL2main.lib since SDL 2.0.12. [#3988] +- Examples: Android: Make Android example build compatible with Gradle 7.0. (#3446) +- Docs: Improvements to description of using colored glyphs/emojis. (#4169, #3369) +- Docs: Improvements to minor mistakes in documentation comments (#3923) [@ANF-Studios] + +Docking+Viewports Branch: + +- [Breaking] Removed io.ConfigDockingWithShift config option. Behavior always equivalent to having the + option set to false (dock/undock by default, hold shift to avoid docking). (#2109) +- Docking: DockSpace() returns its node ID. +- Docking: Dockspace() never draws a background. (#3924) +- Docking: Undocking nodes/windows covering most of the monitor max their size down to 90% to ease manipulations. +- Docking: Docking node tab bar honors ItemInnerSpacing.x before first tab. (#4130) +- Docking: Tweak rendering and alignment of dock node menu marker. (#4130) +- Docking: Fixed restoring of tab order within a dockspace or a split node. +- Docking: Fixed reappearing docked windows with no close button showing a tab with extraneous space for one frame. +- Docking: Fixed multiple simultaneously reappearing window from appearing undocked for one frame. +- Viewports: Hotfix for crash in monitor array access, caused by 4b9bc4902. (#3967) +- Backends, Viewports: GLFW: Add a workaround for stuck keys after closing a GLFW window (#3837). +- Backends, Viewports: Vulkan: Rebuild swapchain on VK_SUBOPTIMAL_KHR. (#3881) + + +----------------------------------------------------------------------- + VERSION 1.82 (Released 2021-02-15) +----------------------------------------------------------------------- + +Decorated log: https://github.com/ocornut/imgui/releases/tag/v1.82 + +Breaking Changes: + +- Removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018): + - ImGui::SetScrollHere() --> use ImGui::SetScrollHereY() +- ImDrawList: upgraded AddPolyline()/PathStroke()'s "bool closed" parameter to use "ImDrawFlags flags". + - bool closed = false --> use ImDrawFlags_None, or 0 + - bool closed = true --> use ImDrawFlags_Closed + The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future. + Difference may not be noticeable for most but zealous type-checking tools may report a need to change. +- ImDrawList: upgraded AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags. + - ImDrawCornerFlags_TopLeft --> use ImDrawFlags_RoundCornersTopLeft + - ImDrawCornerFlags_BotRight --> use ImDrawFlags_RoundCornersBottomRight + - ImDrawCornerFlags_None --> use ImDrawFlags_RoundCornersNone etc. + Flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API. + IMPORTANT: The default with rounding > 0.0f is now "round all corners" vs old implicit "round no corners": + - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use) + - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use) + - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use) + - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use)! + - this ONLY matters for hardcoded use of 0 with rounding > 0.0f. + - fix by using named ImDrawFlags_RoundCornersNone or rounding == 0.0f! + - this is technically the only real breaking change which we can't solve automatically (it's also uncommon). + The old ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" + and we sometimes encouraged using them as shortcuts. As a result the legacy path still support use of hardcoded ~0 + or any value from 0x1 or 0xF. They will behave the same with legacy paths enabled (will assert otherwise). + Courtesy of legacy untangling commity: [@rokups, @ocornut, @thedmd] +- ImDrawList: clarified that PathArcTo()/PathArcToFast() won't render with radius < 0.0f. Previously it sorts + of accidentally worked but would lead to counter-clockwise paths which and have an effect on anti-aliasing. +- InputText: renamed ImGuiInputTextFlags_AlwaysInsertMode to ImGuiInputTextFlags_AlwaysOverwrite, old name was an + incorrect description of behavior. Was ostly used by memory editor. Kept inline redirection function. (#2863) +- Moved 'misc/natvis/imgui.natvis' to 'misc/debuggers/imgui.natvis' as we will provide scripts for other debuggers. +- Style: renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) + to style.CircleTessellationMaxError (new default = 0.30f) as its meaning changed. (#3808) [@thedmd] +- Win32+MinGW: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly + disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their + imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. (#2590, #738) [@actboy168] + *EDIT* Undid in 1.84. +- Backends: Win32: Pragma linking with dwmapi.lib (Vista-era, ~9 kb). MinGW users will need to link with -ldwmapi. + +Other Changes: + +- Window, Nav: Fixed crash when calling SetWindowFocus(NULL) at the time a new window appears. (#3865) [@nem0] +- Window: Shrink close button hit-testing region when it covers an abnormally high portion of the window visible + area (e.g. when window is collapsed + moved in a corner) to facilitate moving the window away. (#3825) +- Nav: Various fixes for losing gamepad/keyboard navigation reference point when a window reappears or + when it appears while gamepad/keyboard are not being used. (#787) +- Drags: Fixed crash when using DragScalar() directly (not via common wrapper like DragFloat() etc.) + with ImGuiSliderFlags_AlwaysClamp + only one of either p_min or p_max set. (#3824) [@harry75369] +- Drags, Sliders: Fixed a bug where editing value would use wrong number if there were digits right after + format specifier (e.g. using "%f123" as a format string). [@rokups] +- Drags, Sliders: Fixed a bug where using custom formatting flags (',$,_) supported by stb_sprintf.h + would cause incorrect value to be displayed. (#3604) [@rokups] +- Drags, Sliders: Support ImGuiSliderFlags_Logarithmic flag with integers. Because why not? (#3786) +- Tables: Fixed unaligned accesses when using TableSetBgColor(ImGuiTableBgTarget_CellBg). (#3872) +- IsItemHovered(): fixed return value false positive when used after EndChild(), EndGroup() or widgets using + either of them, when the hovered location is located within a child window, e.g. InputTextMultiline(). + This is intended to have no side effects, but brace yourself for the possible comeback.. (#3851, #1370) +- Drag and Drop: can use BeginDragDropSource() for other than the left mouse button as long as the item + has an ID (for ID-less items will add new functionalities later). (#1637, #3885) +- ImFontAtlas: Added 'bool TexPixelsUseColors' output to help backend decide of underlying texture format. (#3369) + This can currently only ever be set by the Freetype renderer. +- imgui_freetype: Added ImGuiFreeTypeBuilderFlags_Bitmap flag to request Freetype loading bitmap data. + This may have an effect on size and must be called with correct size values. (#3879) [@metarutaiga] +- ImDrawList: PathArcTo() now supports "int num_segments = 0" (new default) and adaptively tessellate. + The adaptive tessellation uses look up tables, tends to be faster than old PathArcTo() while maintaining + quality for large arcs (tessellation quality derived from "style.CircleTessellationMaxError") (#3491) [@thedmd] +- ImDrawList: PathArcToFast() also adaptively tessellate efficiently. This means that large rounded corners + in e.g. hi-dpi settings will generally look better. (#3491) [@thedmd] +- ImDrawList: AddCircle, AddCircleFilled(): Tweaked default segment count calculation to honor MaxError + with more accuracy. Made default segment count always even for better looking result. (#3808) [@thedmd] +- Misc: Added GetAllocatorFunctions() to facilitate sharing allocators across DLL boundaries. (#3836) +- Misc: Added 'debuggers/imgui.gdb' and 'debuggers/imgui.natstepfilter' (along with existing 'imgui.natvis') + scripts to configure popular debuggers into skipping trivial functions when using StepInto. [@rokups] +- Backends: Android: Added native Android backend. (#3446) [@duddel] +- Backends: Win32: Added ImGui_ImplWin32_EnableAlphaCompositing() to facilitate experimenting with + alpha compositing and transparent windows. (#2766, #3447 etc.). +- Backends: OpenGL, Vulkan, DX9, DX10, DX11, DX12, Metal, WebGPU, Allegro: Rework blending equation to + preserve alpha in output buffer (using SrcBlendAlpha = ONE, DstBlendAlpha = ONE_MINUS_SRC_ALPHA consistently + accross all backends), facilitating compositing of the output buffer with another buffer. + (#2693, #2764, #2766, #2873, #3447, #3813, #3816) [@ocornut, @thedmd, @ShawnM427, @Ubpa, @aiekick] +- Backends: DX9: Fix to support IMGUI_USE_BGRA_PACKED_COLOR. (#3844) [@Xiliusha] +- Backends: DX9: Fix to support colored glyphs, using newly introduced 'TexPixelsUseColors' info. (#3844) +- Examples: Android: Added Android + GL ES3 example. (#3446) [@duddel] +- Examples: Reworked setup of clear color to be compatible with transparent values. +- CI: Use a dedicated "scheduled" workflow to trigger scheduled builds. Forks may disable this workflow if + scheduled builds builds are not required. [@rokups] +- Log/Capture: Added LogTextV, a va_list variant of LogText. [@PathogenDavid] + +Docking+Viewports Branch: + +- Viewports: Fix setting of ImGuiViewportFlags_NoRendererClear. (#3213) +- Viewports: Added GetViewportPlatformMonitor() with a safety net to keep code portable. +- Viewports, Backends: SDL: Fix missing ImGuiBackendFlags_HasSetMousePos flag in docking branch. +- Viewports, Backends: GLFW: Fix application of WantSetMousePos. (#1542, #787) ----------------------------------------------------------------------- VERSION 1.81 (Released 2021-02-10) ----------------------------------------------------------------------- +Decorated log: https://github.com/ocornut/imgui/releases/tag/v1.81 + Breaking Changes: - ListBox helpers: @@ -135,9 +440,9 @@ Other Changes: - Code using (0,0) as a way to signify "upper-left of the host window" should use GetMainViewport()->Pos. - Code using io.DisplaySize as a way to signify "size of the host window" should use GetMainViewport()->Size. - We are also exposing a work area in ImGuiViewport ('WorkPos', 'WorkSize' vs 'Pos', 'Size' for full area): - - For a Platform Window, the work area is generally the full area minus space used by menu-bars. + - For a Platform Window, the work area is generally the full area minus space used by menu-bars. - For a Platform Monitor, the work area is generally the full area minus space used by task-bars. - - All of this has been the case in 'docking' branch for a long time. What we've done is merely merging + - All of this has been the case in 'docking' branch for a long time. What we've done is merely merging a small chunk of the multi-viewport logic into 'master' to standardize some concepts ahead of time. - Tables: Fixed PopItemWidth() or multi-components items not restoring per-colum ItemWidth correctly. (#3760) - Window: Fixed minor title bar text clipping issue when FramePadding is small/zero and there are no @@ -181,6 +486,17 @@ Other Changes: - Examples: WebGPU: Added Emscripten+WebGPU example. (#3632) [@bfierz] - Backends: GLFW: Added ImGui_ImplGlfw_InitForOther() initialization call to use with non OpenGL API. (#3632) +Docking+Viewports Branch: + +- Docking: Fix losing docking information on closed windows for which the hosting node was split. (#3716) [@GamingMinds-DanielC] +- Docking: Fix gap in hit test hole when using ImGuiDockNodeFlags_PassthruCentralNode touching the edge of a viewport. (#3733) +- Viewports: (Breaking) removed ImGuiPlatformIO::MainViewport which is now pretty much unused and duplicate + (and misleading as we will evolve the concept). +- Viewports: (Breaking) turned ImGuiViewport::GetWorkPos(), ImGuiViewport::GetWorkSize() into regular fields + (WorkPos, WorkSize) before exposing in master branch. +- Viewports: Fix issue inferring viewport z-order when new popups gets created. (#3734) + Metrics updates. +- Viewports, Backends: Vulkan: handle VK_ERROR_OUT_OF_DATE_KHR when resizing secondary viewport (#3766, #3758) + ----------------------------------------------------------------------- VERSION 1.80 (Released 2021-01-21) @@ -296,9 +612,18 @@ Other Changes: - Examples: DirectX12: Move ImGui::Render() call above the first barrier to clarify its lack of effect on the graphics pipe. - CI: Fix testing for Windows DLL builds. (#3603, #3601) [@iboB] - Docs: Improved the wiki and added a https://github.com/ocornut/imgui/wiki/Useful-Widgets page. [@Xipiryon] + [2021/05/20: moved to https://github.com/ocornut/imgui/wiki/Useful-Extensions] - Docs: Split examples/README.txt into docs/BACKENDS.md and docs/EXAMPLES.md, and improved them. - Docs: Consistently renamed all occurrences of "binding" and "back-end" to "backend" in comments and docs. +Docking+Viewports Branch: + +- Docking: Docked windows honor change of tab and text colors. (#2771) +- Docking: Support for appending into existing tab-bar made to work with Docking + internal helper DockNodeBeginAmendTabBar(). +- Docking: Added experimental TabItemFlagsOverrideSet to ImGuiWindowClass. +- Viewports: Fixed incorrect whitening of popups above a modal if both use their own viewport. +- Viewports: Backends: Vulkan: Fixed build, removed extraneous pipeline creation. (#3459, #3579) + ----------------------------------------------------------------------- VERSION 1.79 (Released 2020-10-08) @@ -322,7 +647,7 @@ Breaking Changes: - Renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete). - Renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), REVERTED CHANGE FROM 1.77. For variety of reason this is more self-explanatory and less error-prone. Kept inline redirection function. -- Removed return value from OpenPopupOnItemClick() - returned true on mouse release on item - because it +- Removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item - because it is inconsistent with other popups API and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. @@ -393,6 +718,21 @@ Other Changes: - Examples: DX12: Added '#define ImTextureID ImU64' in project and build files to also allow building on 32-bit systems. Added project to default Visual Studio solution file. (#301) +Docking+Viewports Branch: + +- Docking: DockSpace() emits ItemSize() properly (useful when not filling all space). +- Docking: Fixed docking while hovering a child window. (#3420) broken by 85a661d. Improve metrics debugging. +- Docking: Fix honoring payload filter with overlapping nodes (we incorrectly over-relied on g.HoveredDockNode + when making change for #3398). +- Docking: Fix handling of WindowMenuButtonPosition == ImGuiDir_None in Docking Nodes. (#3499) +- Viewports: Fixed a rare edge-case if the window targeted by CTRL+Tab stops being rendered. +- Viewports, Backends: DX12: Make secondary viewport format match main viewport one (#3462) {@BeastLe9enD] +- Viewports: Backends: Vulkan: Removed unused shader code. Fix leaks. Avoid unnecessary pipeline creation for main + viewport. (#3459) + Add ImGui_ImplVulkanH_CreateWindowSwapChain in ImGui_ImplVulkanH_CreateOrResizeWindow(). +- Viewports: Backends: DirectX9: Recover from D3DERR_DEVICELOST on secondary viewports. (#3424) +- Viewports, Backends: Win32: Fix toggling of ImGuiViewportFlags_TopMost (#3477) [@Kodokuna] +- Viewports: Backends: GLFW: Workaround for cases where glfwGetMonitorWorkarea fails (#3457) [@dougbinks] + ----------------------------------------------------------------------- VERSION 1.78 (Released 2020-08-18) @@ -494,6 +834,23 @@ Other Changes: - Examples: Vulkan: Fixed GLFW+Vulkan and SDL+Vulkan clear color not being set. (#3390) [@RoryO] - CI: Emscripten has stopped their support for their fastcomp backend, switching to latest sdk [@Xipiryon] +Docking+Viewports Branch: + +- Docking: Made DockBuilderAddNode() automatically call DockBuilderRemoveNode(). (#3399, #2109) +- Docking: Storing HoveredDockNode in context which can be useful for easily detecting e.g. hovering an + empty node. (#3398) +- Docking: Fixed docking overlay bits appearing at (0,0), because of 43bd80a. Most typically noticeable + when disabling multi-viewport. +- Docking: Workaround recovery for node created without the _DockSpace flags later becoming a DockSpace. (#3340) +- Docking: Rework size allocations to recover when there's no enough room for nodes + do not hold on + _WantLockSizeOnce forever. (#3328) +- Docking: Rework size allocation to allow user code to override node sizes. Not all edge cases will be + properly handled but this is a step toward toolbar emitting size constraints. +- Docking: Added experimental flags to perform more docking filtering and disable resize per axis. + Designed for toolbar patterns. +- Viewports, Backends, GLFW: Use GLFW_MOUSE_PASSTHROUGH when available. +- Viewports, Backends: DX12: Fixed issue on shutdown when viewports are disabled. (#3347) + ----------------------------------------------------------------------- VERSION 1.77 (Released 2020-06-29) @@ -585,6 +942,17 @@ Other Changes: - Examples: Apple: Fixed example_apple_metal and example_apple_opengl2 using imgui_impl_osx.mm not forwarding right and center mouse clicks. (#3260) [@nburrus] +Docking+Viewports Branch: + +- Viewports: Don't set ImGuiViewportFlags_NoRendererClear when ImGuiWindowFlags_NoBackground is set. (#3213) +- Viewports: Report minimized viewports as zero DisplaySize to be consistent with main branch. (#1542) +- Docking, Settings: Allow reload of settings data at runtime. (#2573) +- Backends, GLFW: Fix windows resizing incorrectly on Linux due to GLFW firing window positioning + callbacks on next frame after window is resized manually. (#2117) +- Backends: DX12: Fix OBJECT_DELETED_WHILE_STILL_IN_USE on viewport resizing. (#3210) +- Backends: DX12: Fix for crash caused by early resource release. (#3121) +- Backends, Win32: Request monitor update when DPI awareness is enabled to make sure they have the correct DPI settings. + ----------------------------------------------------------------------- VERSION 1.76 (Released 2020-04-12) @@ -657,6 +1025,20 @@ Other Changes: - Examples: SDL+DX11: Fixed resizing main window. (#3057) [@joeslay] - Examples: Added SDL+Metal example application. (#3017) [@coding-jackalope] +Docking+Viewports Branch: + +- Docking: Fixed assert preventing dockspace from being created instead a hidden tab. (#3101) +- Viewports: Fixed secondary viewports accidentally merging into a minimized host viewport. (#3118) +- Viewports, Docking: Added per-viewport work area system for e.g. menu-bars. Fixed DockspaceOverViewport() + and demo code (overlay etc) accordingly. (#3035, #2889, #2474, #1542, #2109) +- Viewports: Improve menu positioning in multi-monitor setups. [@rokups] +- Viewports: Software mouse cursor is also scaled by current DpiScale. (amend #939) +- Viewports: Avoid manually clipping resize grips and borders, which messes up with automation ability + to locate those items. Also simpler and more standard. +- Viewports: Fix for UWP in the imgui_impl_win32.cpp IME handler. (#2895, #2892). +- Viewports: Bunch of extra of comments to facilitate setting up multi-viewports. +- Viewports, GLFW: Avoid using window positioning workaround for GLFW 3.3+ versions that have it fixed. + ----------------------------------------------------------------------- VERSION 1.75 (Released 2020-02-10) @@ -755,6 +1137,21 @@ Other Changes: - Examples: Metal: Wrapped main loop in @autoreleasepool block to ensure allocations get freed even if underlying system event loop gets paused due to app nap. (#2910, #2917) [@bear24rw] +Docking+Viewports Branch: + +- Docking + Nav: Fixed messed up Ctrl+Tab order with docked windows. +- Docking + Nav: Fixed failing to restore NavId when refocusing a child within a docked window. +- Docking + Nav: Fixed failing to restore NavId when refocusing due to missing nav window (when + it stops being submitted). +- Docking: Fixed a bug where the tab bar of a hidden dockspace would keep requesting focus. (#2960) +- Docking: Added experimental DockNodeFlagsOverrideSet/DockNodeFlagsOverrideClear flags in ImGuiWindowClass + (currently experimenting with toolbar idioms). +- Viewports: Fix resizing viewport-owning windows when mouse pos is outside the InnerClipRect + (can happen with OS decoration enabled). +- Viewports: Preserve last known size for minimized main viewport to be consistent with secondary viewports. +- Backends: SDL: Honor NoTaskBarIcon flag under non Win32 OS. (#2117) +- Backends: GLFW, SDL: Platform monitors declared properly even if multi-viewport is not enabled. + ----------------------------------------------------------------------- VERSION 1.74 (Released 2019-11-25) @@ -834,6 +1231,14 @@ Other Changes: - CI: Set up a bunch of continuous-integration tests using GitHub Actions. We now compile many of the example applications on Windows, Linux, MacOS, iOS, Emscripten. Removed Travis integration. (#2865) [@rokups] +Docking+Viewports Branch: + +- Docking: Can undock from the small triangle button. (#2109,. #2645) +- Docking: Fixed node->HasCloseButton not honoring ImGuiDockNodeFlags_NoCloseButton in a floating node, + leading to empty space at the right of tab-bars with those flags. (#2109) +- Docking: Made docked windows not use style.ChildRounding. +- Multi-viewports: Added multi-viewport support in the DX12 back-end. (#2851) [@obfuscate] + ----------------------------------------------------------------------- VERSION 1.73 (Released 2019-09-24) @@ -896,6 +1301,28 @@ Other Changes: - Misc: Updated stb_rect_pack.h from 0.99 to 1.00 (fixes by @rygorous: off-by-1 bug in best-fit heuristic, fix handling of rectangles too large to fit inside texture). (#2762) [@tido64] +Docking+Viewports Branch: + +- Docking: Fix BeginDocked() path that creates node so that SetNextWindowDockID() doesn't immediately discard the node. (#2109) +- Docking: Fix for node created at the same time as windows that are still resizing (typically with + io.ConfigDockingAlwaysTabBar) to not be zero/min sized. (#2109). The fix delays their visibility by one frame, + which is not ideal but not very problematic as the .ini data gets populated after that. +- Docking: Fix a crash that could occur with a malformed ini file (DockNode Parent value pointing to a missing node). +- Viewport: Fix modal/popup window being stuck in unowned hidden viewport associated to fallback window without stealing + it back. Fix modal reference viewport when opened outside of another window. (#1542) +- Viewport: Modals don't need to set ImGuiViewportFlags_NoFocusOnClick, this also mitigate the issue described by #2445, + which becomes particularly bad with unfocused modal. (#1542) +- Viewport: better case case where host window gets moved and resized simultaneous (toggling maximized state). + There's no perfect solution there, than using io.ConfigViewportsNoAutoMerge = false. (#1542) +- Viewport, Docking: Fixed incorrect assignment of IsFallbackWindow which would tag dock node host windows created + in NewFrame() as such, messing with popup viewport inheritance. +- Viewport: Fixed issue where resize grip would display as hovered while mouse is still off the OS bounds so a click + would miss it and focus the OS window behind expected one. (#1542) +- Viewport: Fix to allow multiple shutdown / calls to DestroyPlatformWindows(). (#2769) +- Viewport: Backends: GLFW: Fix setting window size on macOS (#2767, #2117) [@rokups] +- Viewport: Backends: GLFW+Linux: Fix window having incorrect size after uncollapse. (#2756, #2117) [@rokups] +- Viewport: Backends: DirectX9: Workaround for windows not refreshing when main viewport has no draw call. (#2560) + ----------------------------------------------------------------------- VERSION 1.72b (Released 2019-07-31) @@ -998,6 +1425,25 @@ Other Changes: (#2482, #2632) [@josiahmanson] - Examples: Added SDL2+DirectX11 example application. (#2632, #2612, #2482) [@vincenthamm] +Docking+Viewports Branch: + +- Docking: Making it possible to undock a node by clicking on the tab bar / title bar for the node. (#2645). +- Docking: Explicitly inhibit constraint when docked for now. Fix clipping issue related to constraints. (#2690). +- Docking: Fixed dragging/resizing from OS decoration not marking settings as dirty. +- Docking: Renamed io.ConfigDockingTabBarOnSingleWindows to io.ConfigDockingAlwaysTabBar. + Added ImGuiWindowClass::DockingAlwaysTabBar to set on individual windows. +- Docking: Perform simple check: assert if Docking or Viewport are enabled exactly on frame 1 (instead of frame 0 + or later), which is a common user error leading to loss of .ini data on load. +- Docking: Fix so that an appearing window making a dock node reappear won't have a zero-size on its first frame. +- Docking: Fixed using ImGuiDockNodeFlags_AutoHideTabBar with io.ConfigDockingTabBarOnSingleWindows. +- Docking: Added ImGuiDockNode to .natvis file. +- Docking: Fixed support for large meshes in GetBackgroundDrawList(), GetForegroundDrawList(). (#2638) +- Viewport: Fix monitor dpi info not being copied to main viewport when multi-viewports are not enabled. (#2621, #1676) +- Viewport: Refactored ImGuiWindowClass's ViewportFlagsOverrideMask + ViewportFlagsOverrideValue into + ViewportFlagsOverrideSet + ViewportFlagsOverrideClear which appears easier to grasp. (#1542) +- Viewport: Added ImGuiViewportFlags_NoAutoMerge to prevent merging into host viewport in a per-window basis + via the ImGuiWindowClass override mechanism. (#1542) + ----------------------------------------------------------------------- VERSION 1.71 (Released 2019-06-12) @@ -1262,7 +1708,7 @@ Breaking Changes: - Removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already). - Made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). - If for some reason your time step calculation gives you a zero value, replace it with a arbitrary small value! + If for some reason your time step calculation gives you a zero value, replace it with a arbitrarily small value! Other Changes: diff --git a/examples/interactive/imgui-1.81/docs/EXAMPLES.md b/examples/interactive/imgui-1.83/docs/EXAMPLES.md similarity index 95% rename from examples/interactive/imgui-1.81/docs/EXAMPLES.md rename to examples/interactive/imgui-1.83/docs/EXAMPLES.md index 35d4bec3..979539bf 100644 --- a/examples/interactive/imgui-1.81/docs/EXAMPLES.md +++ b/examples/interactive/imgui-1.83/docs/EXAMPLES.md @@ -3,7 +3,11 @@ _(You may browse this at https://github.com/ocornut/imgui/blob/master/docs/EXAMP ## Dear ImGui: Examples **The [examples/](https://github.com/ocornut/imgui/blob/master/examples) folder example applications (standalone, ready-to-build) for variety of -platforms and graphics APIs.** They all use standard backends from the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder. +platforms and graphics APIs.** They all use standard backends from the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder (see [BACKENDS.md](https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md)). + +The purpose of Examples is to showcase integration with backends, let you try Dear ImGui, and guide you toward +integrating Dear ImGui in your own application/game/engine. +**Once Dear ImGui is setup and running, run and refer to `ImGui::ShowDemoWindow()` in imgui_demo.cpp for usage of the end-user API.** You can find Windows binaries for some of those example applications at: http://www.dearimgui.org/binaries @@ -79,6 +83,10 @@ Changelog, so if you want to update them later it will be easier to catch up wit Allegro 5 example.
= main.cpp + imgui_impl_allegro5.cpp +[example_android_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_android_opengl3/)
+Android + OpenGL3 (ES) example.
+= main.cpp + imgui_impl_android.cpp + imgui_impl_opengl3.cpp + [example_apple_metal/](https://github.com/ocornut/imgui/blob/master/examples/example_metal/)
OSX & iOS + Metal example.
= main.m + imgui_impl_osx.mm + imgui_impl_metal.mm
diff --git a/examples/interactive/imgui-1.81/docs/FAQ.md b/examples/interactive/imgui-1.83/docs/FAQ.md similarity index 88% rename from examples/interactive/imgui-1.81/docs/FAQ.md rename to examples/interactive/imgui-1.83/docs/FAQ.md index 2d5e8060..9ff5df0f 100644 --- a/examples/interactive/imgui-1.81/docs/FAQ.md +++ b/examples/interactive/imgui-1.83/docs/FAQ.md @@ -16,12 +16,12 @@ or view this file with any Markdown viewer. | [Which version should I get?](#q-which-version-should-i-get) | | **Q&A: Integration** | | **[How to get started?](#q-how-to-get-started)** | -| **[How can I tell whether to dispatch mouse/keyboard to Dear ImGui or to my application?](#q-how-can-i-tell-whether-to-dispatch-mousekeyboard-to-dear-imgui-or-to-my-application)** | +| **[How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?](#q-how-can-i-tell-whether-to-dispatch-mousekeyboard-to-dear-imgui-or-my-application)** | | [How can I enable keyboard or gamepad controls?](#q-how-can-i-enable-keyboard-or-gamepad-controls) | | [How can I use this on a machine without mouse, keyboard or screen? (input share, remote display)](#q-how-can-i-use-this-on-a-machine-without-mouse-keyboard-or-screen-input-share-remote-display) | -| [I integrated Dear ImGui in my engine and little squares are showing instead of text..](#q-i-integrated-dear-imgui-in-my-engine-and-little-squares-are-showing-instead-of-text) | -| [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | -| [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries..](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | +| [I integrated Dear ImGui in my engine and little squares are showing instead of text...](#q-i-integrated-dear-imgui-in-my-engine-and-little-squares-are-showing-instead-of-text) | +| [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | +| [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | | **Q&A: Usage** | | **[Why is my widget not reacting when I click on it?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?](#q-why-is-my-widget-not-reacting-when-i-click-on-it)** | | [How can I display an image? What is ImTextureID, how does it work?](#q-how-can-i-display-an-image-what-is-imtextureid-how-does-it-work)| @@ -47,15 +47,15 @@ or view this file with any Markdown viewer. ### Q: Where is the documentation? -**This library is poorly documented at the moment and expects of the user to be acquainted with C/C++.** +**This library is poorly documented at the moment and expects the user to be acquainted with C/C++.** +- The [Wiki](https://github.com/ocornut/imgui/wiki) is a hub to many resources and links. - Dozens of standalone example applications using e.g. OpenGL/DirectX are provided in the [examples/](https://github.com/ocornut/imgui/blob/master/examples/) folder to explain how to integrate Dear ImGui with your own engine/application. You can run those applications and explore them. - See demo code in [imgui_demo.cpp](https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp) and particularly the `ImGui::ShowDemoWindow()` function. The demo covers most features of Dear ImGui, so you can read the code and see its output. - See documentation: [Backends](https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md), [Examples](https://github.com/ocornut/imgui/blob/master/docs/EXAMPLES.md), [Fonts](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md). - See documentation and comments at the top of [imgui.cpp](https://github.com/ocornut/imgui/blob/master/imgui.cpp) + general API comments in [imgui.h](https://github.com/ocornut/imgui/blob/master/imgui.h). -- The [Wiki](https://github.com/ocornut/imgui/wiki) has many resources and links. - The [Glossary](https://github.com/ocornut/imgui/wiki/Glossary) page may be useful. -- The [Issues](https://github.com/ocornut/imgui/issues) section can be searched for past questions and issues. -- Your programming IDE is your friend, find the type or function declaration to find comments associated to it. +- The [Issues](https://github.com/ocornut/imgui/issues) and [Discussions](https://github.com/ocornut/imgui/discussions) sections can be searched for past questions and issues. +- Your programming IDE is your friend, find the type or function declaration to find comments associated with it. - The `ImGui::ShowMetricsWindow()` function exposes lots of internal information and tools. Although it is primary designed as a debugging tool, having access to that information tends to help understands concepts. ##### [Return to Index](#index) @@ -81,9 +81,6 @@ You may use the [docking](https://github.com/ocornut/imgui/tree/docking) branch Many projects are using this branch and it is kept in sync with master regularly. -You may merge in the [tables](https://github.com/ocornut/imgui/tree/tables) branch which includes: -- [Table features](https://github.com/ocornut/imgui/issues/2957) - ##### [Return to Index](#index) ---- @@ -94,24 +91,35 @@ You may merge in the [tables](https://github.com/ocornut/imgui/tree/tables) bran Read [EXAMPLES.md](https://github.com/ocornut/imgui/blob/master/docs/EXAMPLES.md).
Read [BACKENDS.md](https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md).
-Read `PROGRAMMER GUIDE` section of [imgui.cpp](https://github.com/ocornut/imgui/blob/master/imgui.cpp). +Read `PROGRAMMER GUIDE` section of [imgui.cpp](https://github.com/ocornut/imgui/blob/master/imgui.cpp).
+The [Wiki](https://github.com/ocornut/imgui/wiki) is a hub to many resources and links. ##### [Return to Index](#index) --- -### Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or to my application? +### Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application? You can read the `io.WantCaptureMouse`, `io.WantCaptureKeyboard` and `io.WantTextInput` flags from the ImGuiIO structure. +- When `io.WantCaptureMouse` is set, you need to discard/hide the mouse inputs from your underlying application. +- When `io.WantCaptureKeyboard` is set, you need to discard/hide the keyboard inputs from your underlying application. +- When `io.WantTextInput` is set, you can notify your OS/engine to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + +Important: you should always pass your mouse/keyboard inputs to Dear ImGui, regardless of the value `io.WantCaptureMouse`/`io.WantCaptureKeyboard`. This is because e.g. we need to detect that you clicked in the void to unfocus its own windows, and other reasons. -e.g. `if (ImGui::GetIO().WantCaptureMouse) { ... }` +```cpp +void MyLowLevelMouseButtonHandler(int button, bool down) +{ + // (1) ALWAYS forward mouse data to ImGui! This is automatic with default backends. With your own backend: + ImGuiIO& io = ImGui::GetIO(); + io.MouseDown[button] = down; -- When `io.WantCaptureMouse` is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application. -- When `io.WantCaptureKeyboard` is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application. -- When `io.WantTextInput` is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + // (2) ONLY forward mouse data to your underlying app/game. + if (!io.WantCaptureMouse) + my_game->HandleMouseData(...); +} +``` -**Note:** You should always pass your mouse/keyboard inputs to Dear ImGui, even when the io.WantCaptureXXX flag are set false. - This is because imgui needs to detect that you clicked in the void to unfocus its own windows. **Note:** The `io.WantCaptureMouse` is more correct that any manual attempt to "check if the mouse is hovering a window" (don't do that!). It handle mouse dragging correctly (both dragging that started over your application or over a Dear ImGui window) and handle e.g. popup and modal windows blocking inputs. @@ -151,16 +159,17 @@ Console SDK also sometimes provide equivalent tooling or wrapper for Synergy-lik --- -### Q: I integrated Dear ImGui in my engine and little squares are showing instead of text.. -This usually means that: your font texture wasn't uploaded into GPU, or your shader or other rendering state are not reading from the right texture (e.g. texture wasn't bound). -If this happens using the standard backends it is probably that the texture failed to upload, which could happens if for some reason your texture is too big. Also see [docs/FONTS.md](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md). +### Q: I integrated Dear ImGui in my engine and little squares are showing instead of text... +Your renderer is not using the font texture correctly or it hasn't be uploaded to GPU. +- If this happens using the standard backends: A) have you modified the font atlas after `ImGui_ImplXXX_NewFrame()`? B) maybe the texture failed to upload, which could happens if for some reason your texture is too big. Also see [docs/FONTS.md](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md). +- If this happens with a custom backend: make sure you have uploaded the font texture to the GPU, that all shaders are rendering states are setup properly (e.g. texture is bound). Compare your code to existing backends and use a graphics debugger such as [RenderDoc](https://renderdoc.org) to debug your rendering states. ##### [Return to Index](#index) --- -### Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. -### Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries.. +### Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around... +### Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries... You are probably mishandling the clipping rectangles in your render function. Each draw command needs the triangle rendered using the clipping rectangle provided in the ImDrawCmd structure (`ImDrawCmd->CllipRect`). Rectangles provided by Dear ImGui are defined as @@ -216,8 +225,11 @@ End(); - If you have a same ID twice in the same location, you'll have a conflict: ```cpp +Begin("MyWindow"); Button("OK"); -Button("OK"); // ID collision! Interacting with either button will trigger the first one. +Button("OK"); // ERROR: ID collision with the first button! Interacting with either button will trigger the first one. +Button(""); // ERROR: ID collision with Begin("MyWindow")! +End(); ``` Fear not! this is easy to solve and there are many ways to solve it! @@ -229,8 +241,9 @@ are going to be created: ```cpp Begin("MyWindow"); Button("Play"); // Label = "Play", ID = hash of ("MyWindow", "Play") -Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from above -Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from above +Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from other buttons +Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from other buttons +Button("##foo"); // Label = "", ID = hash of ("MyWindow", "##foo") // Different from window End(); ``` - If you want to completely hide the label, but still need an ID: @@ -248,7 +261,7 @@ sprintf(buf, "My game (%f FPS)###MyGame", fps); Begin(buf); // Variable title, ID = hash of "MyGame" ``` - Solving ID conflict in a more general manner: -Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts +Use `PushID()` / `PopID()` to create scopes and manipulate the ID stack, as to avoid ID conflicts within the same window. This is the most convenient way of distinguishing ID when iterating and creating many UI elements programmatically. You can push a pointer, a string or an integer value into the ID stack. @@ -288,7 +301,7 @@ PushID("node"); PopID(); PopID(); ``` -- Tree nodes implicitly creates a scope for you by calling PushID(). +- Tree nodes implicitly creates a scope for you by calling `PushID()`: ```cpp Button("Click"); // Label = "Click", ID = hash of (..., "Click") if (TreeNode("node")) // <-- this function call will do a PushID() for you (unless instructed not to, with a special flag) @@ -362,7 +375,7 @@ ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height)); The renderer function called after ImGui::Render() will receive that same value that the user code passed: ```cpp // Cast ImTextureID / void* stored in the draw command as our texture type -MyTexture* texture = (MyTexture*)pcmd->TextureId; +MyTexture* texture = (MyTexture*)pcmd->GetTexID(); MyEngineBindTexture2D(texture); ``` Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. @@ -452,8 +465,9 @@ ImGui::End(); - To generate colors: you can use the macro `IM_COL32(255,255,255,255)` to generate them at compile time, or use `ImGui::GetColorU32(IM_COL32(255,255,255,255))` or `ImGui::GetColorU32(ImVec4(1.0f,1.0f,1.0f,1.0f))` to generate a color that is multiplied by the current value of `style.Alpha`. - Math operators: if you have setup `IM_VEC2_CLASS_EXTRA` in `imconfig.h` to bind your own math types, you can use your own math types and their natural operators instead of ImVec2. ImVec2 by default doesn't export any math operators in the public API. You may use `#define IMGUI_DEFINE_MATH_OPERATORS` `#include "imgui_internal.h"` to use the internally defined math operators, but instead prefer using your own math library and set it up in `imconfig.h`. - You can use `ImGui::GetBackgroundDrawList()` or `ImGui::GetForegroundDrawList()` to access draw lists which will be displayed behind and over every other dear imgui windows (one bg/fg drawlist per viewport). This is very convenient if you need to quickly display something on the screen that is not associated to a dear imgui window. -- You can also create your own empty window and draw inside it. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags (The `ImGuiWindowFlags_NoDecoration` flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse). Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. -- You can create your own ImDrawList instance. You'll need to initialize them with `ImGui::GetDrawListSharedData()`, or create your own instancing ImDrawListSharedData, and then call your renderer function with your own ImDrawList or ImDrawData data. +- You can also create your own empty window and draw inside it. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags (The `ImGuiWindowFlags_NoDecoration` flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse). Then you can retrieve the ImDrawList* via `GetWindowDrawList()` and draw to it in any way you like. +`- You can create your own ImDrawList instance. You'll need to initialize them with `ImGui::GetDrawListSharedData()`, or create your own instancing ImDrawListSharedData`, and then call your renderer function with your own ImDrawList or ImDrawData data. +- Looking for fun? The [ImDrawList coding party 2020](https://github.com/ocornut/imgui/issues/3606) thread is full of "don't do this at home" extreme uses of the ImDrawList API. ##### [Return to Index](#index) @@ -599,7 +613,8 @@ You may take a look at: - [Quotes](https://github.com/ocornut/imgui/wiki/Quotes) - [Software using Dear ImGui](https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui) -- [Gallery](https://github.com/ocornut/imgui/issues/3488) +- [Sponsors](https://github.com/ocornut/imgui/wiki/Sponsors) +- [Gallery](https://github.com/ocornut/imgui/issues/3793) ##### [Return to Index](#index) @@ -641,11 +656,11 @@ There is an auto-generated [c-api for Dear ImGui (cimgui)](https://github.com/ci # Q&A: Community ### Q: How can I help? -- Businesses: please reach out to `contact AT dearimgui.com` if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding we can hire more people working on this project. +- Businesses: please reach out to `contact AT dearimgui.com` if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we can hire more people working on this project. - Individuals: you can support continued maintenance and development via PayPal donations. See [README](https://github.com/ocornut/imgui/blob/master/docs/README.md). -- If you are experienced with Dear ImGui and C++, look at the [GitHub Issues](https://github.com/ocornut/imgui/issues), look at the [Wiki](https://github.com/ocornut/imgui/wiki), read [docs/TODO.txt](https://github.com/ocornut/imgui/blob/master/docs/TODO.txt) and see how you want to help and can help! +- If you are experienced with Dear ImGui and C++, look at [GitHub Issues](https://github.com/ocornut/imgui/issues), [GitHub Discussions](https://github.com/ocornut/imgui/discussions), the [Wiki](https://github.com/ocornut/imgui/wiki), read [docs/TODO.txt](https://github.com/ocornut/imgui/blob/master/docs/TODO.txt) and see how you want to help and can help! - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. -You may post screenshot or links in the [gallery threads](https://github.com/ocornut/imgui/issues/3488). Visuals are ideal as they inspire other programmers. Disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. +You may post screenshot or links in the [gallery threads](https://github.com/ocornut/imgui/issues/3793). Visuals are ideal as they inspire other programmers. Disclosing your use of Dear ImGui helps the library grow credibility, and help other teams and programmers with taking decisions. - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues or sometimes incomplete PR. ##### [Return to Index](#index) diff --git a/examples/interactive/imgui-1.81/docs/FONTS.md b/examples/interactive/imgui-1.83/docs/FONTS.md similarity index 90% rename from examples/interactive/imgui-1.81/docs/FONTS.md rename to examples/interactive/imgui-1.83/docs/FONTS.md index 474a2007..760877e8 100644 --- a/examples/interactive/imgui-1.81/docs/FONTS.md +++ b/examples/interactive/imgui-1.83/docs/FONTS.md @@ -5,19 +5,18 @@ _(You may browse this at https://github.com/ocornut/imgui/blob/master/docs/FONTS The code in imgui.cpp embeds a copy of 'ProggyClean.ttf' (by Tristan Grimmer), a 13 pixels high, pixel-perfect font used by default. We embed it in the source code so you can use Dear ImGui without any file system access. ProggyClean does not scale smoothly, therefore it is recommended that you load your own file when using Dear ImGui in an application aiming to look nice and wanting to support multiple resolutions. -You may also load external .TTF/.OTF files. +You may also load external .TTF/.OTF files. In the [misc/fonts/](https://github.com/ocornut/imgui/tree/master/misc/fonts) folder you can find a few suggested fonts, provided as a convenience. -**Read the FAQ:** https://www.dearimgui.org/faq (there is a Fonts section!) - -**Use the Discord server**: http://discord.dearimgui.org and not the GitHub issue tracker for basic font loading questions. +**Also read the FAQ:** https://www.dearimgui.org/faq (there is a Fonts section!) ## Index - [Readme First](#readme-first) - [How should I handle DPI in my application?](#how-should-i-handle-dpi-in-my-application) - [Fonts Loading Instructions](#font-loading-instructions) -- [Using Icons](#using-icons) -- [Using FreeType Rasterizer](#using-freetype-rasterizer) +- [Using Icon Fonts](#using-icon-fonts) +- [Using FreeType Rasterizer (imgui_freetype)](#using-freetype-rasterizer-imgui_freetype) +- [Using Colorful Glyphs/Emojis](#using-colorful-glyphsemojis) - [Using Custom Glyph Ranges](#using-custom-glyph-ranges) - [Using Custom Colorful Icons](#using-custom-colorful-icons) - [Using Font Data Embedded In Source Code](#using-font-data-embedded-in-source-code) @@ -28,7 +27,7 @@ In the [misc/fonts/](https://github.com/ocornut/imgui/tree/master/misc/fonts) fo --------------------------------------- ## Readme First -- All loaded fonts glyphs are rendered into a single texture atlas ahead of time. Calling either of `io.Fonts->GetTexDataAsAlpha8()`, `io.Fonts->GetTexDataAsRGBA32()` or `io.Fonts->Build()` will build the atlas. +- All loaded fonts glyphs are rendered into a single texture atlas ahead of time. Calling either of `io.Fonts->GetTexDataAsAlpha8()`, `io.Fonts->GetTexDataAsRGBA32()` or `io.Fonts->Build()` will build the atlas. - You can use the style editor `ImGui::ShowStyleEditor()` in the "Fonts" section to browse your fonts and understand what's going on if you have an issue. You can also reach it in `Demo->Tools->Style Editor->Fonts`: @@ -162,13 +161,13 @@ Some solutions: ##### [Return to Index](#index) -## Using Icons +## Using Icon Fonts Using an icon font (such as [FontAwesome](http://fontawesome.io) or [OpenFontIcons](https://github.com/traverseda/OpenFontIcons)) is an easy and practical way to use icons in your Dear ImGui application. A common pattern is to merge the icon font within your main font, so you can embed icons directly from your strings without having to change fonts back and forth. To refer to the icon UTF-8 codepoints from your C++ code, you may use those headers files created by Juliette Foucaut: https://github.com/juliettef/IconFontCppHeaders. - + So you can use `ICON_FA_SEARCH` as a string that will render as a "Search" icon. Example Setup: @@ -199,7 +198,7 @@ Here's an application using icons ("Avoyd", https://www.avoyd.com): ##### [Return to Index](#index) -## Using FreeType Rasterizer +## Using FreeType Rasterizer (imgui_freetype) - Dear ImGui uses imstb\_truetype.h to rasterize fonts (with optional oversampling). This technique and its implementation are not ideal for fonts rendered at small sizes, which may appear a little blurry or hard to read. - There is an implementation of the ImFontAtlas builder using FreeType that you can use in the [misc/freetype/](https://github.com/ocornut/imgui/tree/master/misc/freetype) folder. @@ -209,6 +208,28 @@ Here's an application using icons ("Avoyd", https://www.avoyd.com): ##### [Return to Index](#index) +## Using Colorful Glyphs/Emojis + +- Rendering of colored emojis is only supported by imgui_freetype with FreeType 2.10+. +- You will need to load fonts with the `ImGuiFreeTypeBuilderFlags_LoadColor` flag. +- Emojis are frequently encoded in upper Unicode layers (character codes >0x10000) and will need dear imgui compiled with `IMGUI_USE_WCHAR32`. +- Not all types of color fonts are supported by FreeType at the moment. +- Stateful Unicode features such as skin tone modifiers are not supported by the text renderer. + +![colored glyphs](https://user-images.githubusercontent.com/8225057/106171241-9dc4ba80-6191-11eb-8a69-ca1467b206d1.png) + +```cpp +io.Fonts->AddFontFromFileTTF("../../../imgui_dev/data/fonts/NotoSans-Regular.ttf", 16.0f); +static ImWchar ranges[] = { 0x1, 0x1FFFF, 0 }; +static ImFontConfig cfg; +cfg.OversampleH = cfg.OversampleV = 1; +cfg.MergeMode = true; +cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor; +io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguiemj.ttf", 16.0f, &cfg, ranges); +``` + +##### [Return to Index](#index) + ## Using Custom Glyph Ranges You can use the `ImFontGlyphRangesBuilder` helper to create glyph ranges based on text input. For example: for a game where your script is known, if you can feed your entire script to it and only build the characters the game needs. @@ -228,7 +249,7 @@ io.Fonts->Build(); // Build the atlas while ## Using Custom Colorful Icons -**(This is a BETA api, use if you are familiar with dear imgui and with your rendering backend)** +As an alternative to rendering colorful glyphs using imgui_freetype with `ImGuiFreeTypeBuilderFlags_LoadColor`, you may allocate your own space in the texture atlas and write yourself into it. **(This is a BETA api, use if you are familiar with dear imgui and with your rendering backend)** - You can use the `ImFontAtlas::AddCustomRect()` and `ImFontAtlas::AddCustomRectFontGlyph()` api to register rectangles that will be packed into the font atlas texture. Register them before building the atlas, then call Build()`. - You can then use `ImFontAtlas::GetCustomRectByIndex(int)` to query the position/size of your rectangle within the texture, and blit/copy any graphics data of your choice into those rectangles. @@ -317,7 +338,7 @@ Some fonts files are available in the `misc/fonts/` folder:
https://fonts.google.com/specimen/Roboto **Cousine-Regular.ttf**, by Steve Matteson -
Digitized data copyright (c) 2010 Google Corporation. +
Digitized data copyright (c) 2010 Google Corporation.
Licensed under the SIL Open Font License, Version 1.1
https://fonts.google.com/specimen/Cousine diff --git a/examples/interactive/imgui-1.81/docs/README.md b/examples/interactive/imgui-1.83/docs/README.md similarity index 86% rename from examples/interactive/imgui-1.81/docs/README.md rename to examples/interactive/imgui-1.83/docs/README.md index 0da03cdf..f51781ee 100644 --- a/examples/interactive/imgui-1.81/docs/README.md +++ b/examples/interactive/imgui-1.83/docs/README.md @@ -105,7 +105,7 @@ Calling the `ImGui::ShowDemoWindow()` function will create a demo window showcas ![screenshot demo](https://raw.githubusercontent.com/wiki/ocornut/imgui/web/v167/v167-misc.png) You should be able to build the examples from sources (tested on Windows/Mac/Linux). If you don't, let us know! If you want to have a quick look at some Dear ImGui features, you can download Windows binaries of the demo app here: -- [imgui-demo-binaries-20200918.zip](https://www.dearimgui.org/binaries/imgui-demo-binaries-20200918.zip) (Windows, 1.78 WIP, built 2020/09/18, master branch) or [older demo binaries](https://www.dearimgui.org/binaries). +- [imgui-demo-binaries-20210331.zip](https://www.dearimgui.org/binaries/imgui-demo-binaries-20210331.zip) (Windows, 1.83 WIP, built 2021/03/31, master branch) or [older demo binaries](https://www.dearimgui.org/binaries). The demo applications are not DPI aware so expect some blurriness on a 4K screen. For DPI awareness in your application, you can load/reload your font at different scale, and scale your style with `style.ScaleAllSizes()` (see [FAQ](https://www.dearimgui.org/faq)). @@ -116,8 +116,8 @@ On most platforms and when using C++, **you should be able to use a combination Integrating Dear ImGui within your custom engine is a matter of 1) wiring mouse/keyboard/gamepad inputs 2) uploading one texture to your GPU/render engine 3) providing a render function that can bind textures and render textured triangles. The [examples/](https://github.com/ocornut/imgui/tree/master/examples) folder is populated with applications doing just that. If you are an experienced programmer at ease with those concepts, it should take you less than two hours to integrate Dear ImGui in your custom engine. **Make sure to spend time reading the [FAQ](https://www.dearimgui.org/faq), comments, and some of the examples/ application!** Officially maintained backends/bindings (in repository): -- Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal, OpenGL (legacy), OpenGL3/ES/ES2 (modern), Vulkan, WebGPU. -- Platforms: GLFW, SDL2, Win32, Glut, OSX. +- Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal, OpenGL/ES/ES2, Vulkan, WebGPU. +- Platforms: GLFW, SDL2, Win32, Glut, OSX, Android. - Frameworks: Emscripten, Allegro5, Marmalade. [Third-party backends/bindings](https://github.com/ocornut/imgui/wiki/Bindings) wiki page: @@ -125,7 +125,7 @@ Officially maintained backends/bindings (in repository): - Frameworks: AGS/Adventure Game Studio, Amethyst, Blender, bsf, Cinder, Cocos2d-x, Diligent Engine, Flexium, GML/Game Maker Studio2, GLEQ, Godot, GTK3+OpenGL3, Irrlicht Engine, LÖVE+LUA, Magnum, Monogame, NanoRT, nCine, Nim Game Lib, Nintendo 3DS & Switch (homebrew), Ogre, openFrameworks, OSG/OpenSceneGraph, Orx, Photoshop, px_render, Qt/QtDirect3D, SDL_Renderer, SFML, Sokol, Unity, Unreal Engine 4, vtk, VulkanHpp, VulkanSceneGraph, Win32 GDI, WxWidgets. - Note that C bindings ([cimgui](https://github.com/cimgui/cimgui)) are auto-generated, you can use its json/lua output to generate bindings for other languages. -[Useful widgets and extensions](https://github.com/ocornut/imgui/wiki/Useful-Widgets) wiki page: +[Useful Extensions/Widgets](https://github.com/ocornut/imgui/wiki/Useful-Extensions) wiki page: - Text editors, node editors, timeline editors, plotting, software renderers, remote network access, memory editors, gizmos etc. Also see [Wiki](https://github.com/ocornut/imgui/wiki) for more links and ideas. @@ -141,9 +141,9 @@ Some of the goals for 2021 are: ### Gallery -For more user-submitted screenshots of projects using Dear ImGui, check out the [Gallery Threads](https://github.com/ocornut/imgui/issues/3488)! +For more user-submitted screenshots of projects using Dear ImGui, check out the [Gallery Threads](https://github.com/ocornut/imgui/issues/3793)! -For a list of third-party widgets and extensions, check out the [Useful Widgets](https://github.com/ocornut/imgui/wiki/Useful-Widgets) wiki page. +For a list of third-party widgets and extensions, check out the [Useful Extensions/Widgets](https://github.com/ocornut/imgui/wiki/Useful-Extensions) wiki page. Custom engine [![screenshot game](https://raw.githubusercontent.com/wiki/ocornut/imgui/web/v149/gallery_TheDragonsTrap-01-thumb.jpg)](https://cloud.githubusercontent.com/assets/8225057/20628927/33e14cac-b329-11e6-80f6-9524e93b048a.png) @@ -162,9 +162,7 @@ See: [Wiki](https://github.com/ocornut/imgui/wiki) for many links, references, a See: [Articles about the IMGUI paradigm](https://github.com/ocornut/imgui/wiki#about-the-imgui-paradigm) to read/learn about the Immediate Mode GUI paradigm. -If you are new to Dear ImGui and have issues with: compiling, linking, adding fonts, wiring inputs, running or displaying Dear ImGui: you can use [Discord server](http://discord.dearimgui.org). - -Otherwise, for any other questions, bug reports, requests, feedback, you may post on https://github.com/ocornut/imgui/issues. Please read and fill the New Issue template carefully. +For questions, bug reports, requests, feedback, you may post on [GitHub Issues](https://github.com/ocornut/imgui/issues) or [GitHub Discussions](https://github.com/ocornut/imgui/discussions). Please read and fill the New Issue template carefully. Private support is available for paying business customers (E-mail: _contact @ dearimgui dot com_). @@ -176,14 +174,14 @@ Advanced users may want to use the `docking` branch with [Multi-Viewport](https: **Who uses Dear ImGui?** -See the [Quotes](https://github.com/ocornut/imgui/wiki/Quotes), [Sponsors](https://github.com/ocornut/imgui/wiki/Sponsors), [Software using dear imgui](https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui) Wiki pages for an idea of who is using Dear ImGui. Please add your game/software if you can! Also see the [Gallery Threads](https://github.com/ocornut/imgui/issues/3488)! +See the [Quotes](https://github.com/ocornut/imgui/wiki/Quotes), [Sponsors](https://github.com/ocornut/imgui/wiki/Sponsors), [Software using dear imgui](https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui) Wiki pages for an idea of who is using Dear ImGui. Please add your game/software if you can! Also see the [Gallery Threads](https://github.com/ocornut/imgui/issues/3793)! How to help ----------- **How can I help?** -- You may participate in the [Discord server](http://discord.dearimgui.org), [GitHub forum/issues](https://github.com/ocornut/imgui/issues). +- See [GitHub Forum/issues](https://github.com/ocornut/imgui/issues) and [Github Discussions](https://github.com/ocornut/imgui/discussions). - You may help with development and submit pull requests! Please understand that by submitting a PR you are also submitting a request for the maintainer to review your code and then take over its maintenance forever. PR should be crafted both in the interest in the end-users and also to ease the maintainer into understanding and accepting it. - See [Help wanted](https://github.com/ocornut/imgui/wiki/Help-Wanted) on the [Wiki](https://github.com/ocornut/imgui/wiki/) for some more ideas. - Have your company financially support this project (please reach by e-mail) @@ -198,13 +196,13 @@ Sponsors Ongoing Dear ImGui development is currently financially supported by users and private sponsors: *Platinum-chocolate sponsors* -- [Blizzard](https://careers.blizzard.com/en-us/openings/engineering/all/all/all/1), [Google](https://github.com/google/filament), [Nvidia](https://developer.nvidia.com/nvidia-omniverse), [Ubisoft](https://montreal.ubisoft.com/en/ubisoft-sponsors-user-interface-library-for-c-dear-imgui/) +- [Blizzard](https://careers.blizzard.com/en-us/openings/engineering/all/all/all/1), [Google](https://github.com/google/filament), [Nvidia](https://developer.nvidia.com/nvidia-omniverse) *Double-chocolate and Salty caramel sponsors* -- [Activision](https://careers.activision.com/c/programmingsoftware-engineering-jobs), [Aras Pranckevičius](https://aras-p.info), [Arkane Studios](https://www.arkane-studios.com), [Framefield](http://framefield.com), [Grinding Gear Games](https://www.grindinggear.com), [Kylotonn](https://www.kylotonn.com), [Next Level Games](https://www.nextlevelgames.com), [RAD Game Tools](http://www.radgametools.com/), [Supercell](http://www.supercell.com) +- [Activision](https://careers.activision.com/c/programmingsoftware-engineering-jobs), [Adobe](https://www.adobe.com/products/medium.html), [Aras Pranckevičius](https://aras-p.info), [Arkane Studios](https://www.arkane-studios.com), [Framefield](http://framefield.com), [Grinding Gear Games](https://www.grindinggear.com), [Kylotonn](https://www.kylotonn.com), [Next Level Games](https://www.nextlevelgames.com), [RAD Game Tools](http://www.radgametools.com/), [O-Net Communications (USA)](http://en.o-netcom.com), [Supercell](https://supercell.com), [Ubisoft](https://montreal.ubisoft.com/en/ubisoft-sponsors-user-interface-library-for-c-dear-imgui) Please see [detailed list of Dear ImGui supporters](https://github.com/ocornut/imgui/wiki/Sponsors) for past sponsors. -From November 2014 to December 2019, ongoing development has also been financially supported by its users on Patreon and through individual donations. +From November 2014 to December 2019, ongoing development has also been financially supported by its users on Patreon and through individual donations. **THANK YOU to all past and present supporters for helping to keep this project alive and thriving!** @@ -216,12 +214,14 @@ Dear ImGui is using software and services provided free of charge for open sourc Credits ------- -Developed by [Omar Cornut](http://www.miracleworld.net) and every direct or indirect [contributors](https://github.com/ocornut/imgui/graphs/contributors) to the GitHub. The early version of this library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com) (PS Vita). +Developed by [Omar Cornut](https://www.miracleworld.net) and every direct or indirect [contributors](https://github.com/ocornut/imgui/graphs/contributors) to the GitHub. The early version of this library was developed with the support of [Media Molecule](https://www.mediamolecule.com) and first used internally on the game [Tearaway](https://tearaway.mediamolecule.com) (PS Vita). Recurring contributors (2020): Omar Cornut [@ocornut](https://github.com/ocornut), Rokas Kupstys [@rokups](https://github.com/rokups), Ben Carter [@ShironekoBen](https://github.com/ShironekoBen). A large portion of work on automation systems, regression tests and other features are currently unpublished. -Omar: "I first discovered the IMGUI paradigm at [Q-Games](http://www.q-games.com) where Atman Binstock had dropped his own simple implementation in the codebase, which I spent quite some time improving and thinking about. It turned out that Atman was exposed to the concept directly by working with Casey. When I moved to Media Molecule I rewrote a new library trying to overcome the flaws and limitations of the first one I've worked with. It became this library and since then I have spent an unreasonable amount of time iterating and improving it." +Sponsoring, support contracts and other B2B transactions are hosted and handled by [Lizardcube](https://www.lizardcube.com). + +Omar: "I first discovered the IMGUI paradigm at [Q-Games](https://www.q-games.com) where Atman Binstock had dropped his own simple implementation in the codebase, which I spent quite some time improving and thinking about. It turned out that Atman was exposed to the concept directly by working with Casey. When I moved to Media Molecule I rewrote a new library trying to overcome the flaws and limitations of the first one I've worked with. It became this library and since then I have spent an unreasonable amount of time iterating and improving it." Embeds [ProggyClean.ttf](http://upperbounds.net) font by Tristan Grimmer (MIT license). diff --git a/examples/interactive/imgui-1.81/docs/TODO.txt b/examples/interactive/imgui-1.83/docs/TODO.txt similarity index 99% rename from examples/interactive/imgui-1.81/docs/TODO.txt rename to examples/interactive/imgui-1.83/docs/TODO.txt index f39552bf..7884c952 100644 --- a/examples/interactive/imgui-1.81/docs/TODO.txt +++ b/examples/interactive/imgui-1.83/docs/TODO.txt @@ -88,6 +88,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - input text: access public fields via a non-callback API e.g. InputTextGetState("xxx") that may return NULL if not active. - input text: flag to disable live update of the user buffer (also applies to float/int text input) (#701) - input text: hover tooltip could show unclamped text + - input text: support for INSERT key to toggle overwrite mode. currently disabled because stb_textedit behavior is unsatisfactory on multi-line. (#2863) - input text: option to Tab after an Enter validation. - input text: add ImGuiInputTextFlags_EnterToApply? (off #218) - input text: easier ways to update buffer (from source char*) while owned. preserve some sort of cursor position for multi-line text. @@ -211,7 +212,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - combo: use clipper: make it easier to disable clipper with a single flag. - combo: flag for BeginCombo to not return true when unchanged (#1182) - - combo: a way/helper to customize the combo preview (#1658) + - combo: a way/helper to customize the combo preview (#1658) -> exeperimental BeginComboPreview() - combo/listbox: keyboard control. need InputText-like non-active focus + key handling. considering keyboard for custom listbox (pr #203) - listbox: multiple selection. - listbox: unselect option (#1208) @@ -243,6 +244,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - shortcuts: local-style shortcut api, e.g. parse "&Save" - shortcuts,menus: global-style shortcut api e.g. "Save (CTRL+S)" -> explicit flag for recursing into closed menu - shortcuts: programmatically access shortcuts "Focus("&Save")) + - menus: hovering a disabled BeginMenu or MenuItem won't close another menu - menus: menu-bar: main menu-bar could affect clamping of windows position (~ akin to modifying DisplayMin) - menus: hovering from menu to menu on a menu-bar has 1 frame without any menu, which is a little annoying. ideally either 0 either longer. - menus: could merge draw call in most cases (how about storing an optional aabb in ImDrawCmd to move the burden of merging in a single spot). @@ -379,7 +381,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - nav/windowing: Resizing window will currently fail with certain types of resizing constraints/callback applied - focus: preserve ActiveId/focus stack state, e.g. when opening a menu and close it, previously selected InputText() focus gets restored (#622) - focus: SetKeyboardFocusHere() on with >= 0 offset could be done on same frame (else latch and modulate on beginning of next frame) - - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787) + - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787, #343) - viewport: make it possible to have no main/hosting viewport - viewport: We set ImGuiViewportFlags_NoFocusOnAppearing in a way that is required for GLFW/SDL binding, but could be handled better without diff --git a/examples/interactive/imgui-1.83/examples/README.txt b/examples/interactive/imgui-1.83/examples/README.txt new file mode 100644 index 00000000..6db2f3c4 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/README.txt @@ -0,0 +1,9 @@ +See BACKENDS and EXAMPLES files in the docs/ folder, or on the web at: https://github.com/ocornut/imgui/tree/master/docs + +Backends = Helper code to facilitate integration with platforms/graphics api (used by Examples + should be used by your app). +Examples = Standalone applications showcasing integration with platforms/graphics api. + +Some Examples have extra README files in their respective directory, please check them too! + +Once Dear ImGui is running (in either examples or your own application/game/engine), +run and refer to ImGui::ShowDemoWindow() in imgui_demo.cpp for the end-user API. diff --git a/examples/interactive/imgui-1.81/examples/example_allegro5/README.md b/examples/interactive/imgui-1.83/examples/example_allegro5/README.md similarity index 87% rename from examples/interactive/imgui-1.81/examples/example_allegro5/README.md rename to examples/interactive/imgui-1.83/examples/example_allegro5/README.md index 0e27f5f6..d6d812ea 100644 --- a/examples/interactive/imgui-1.81/examples/example_allegro5/README.md +++ b/examples/interactive/imgui-1.83/examples/example_allegro5/README.md @@ -23,9 +23,10 @@ You may install Allegro using vcpkg: ``` git clone https://github.com/Microsoft/vcpkg cd vcpkg -.\bootstrap-vcpkg.bat -.\vcpkg install allegro5 -.\vcpkg integrate install ; optional, automatically register include/libs in Visual Studio +bootstrap-vcpkg.bat +vcpkg install allegro5 --triplet=x86-windows ; for win32 +vcpkg install allegro5 --triplet=x64-windows ; for win64 +vcpkg integrate install ; register include / libs in Visual Studio ``` Build: diff --git a/examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj b/examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj rename to examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj index c6c524aa..223138ce 100644 --- a/examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -171,10 +171,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj.filters index 00873a22..7fea78b3 100644 --- a/examples/interactive/imgui-1.81/examples/example_allegro5/example_allegro5.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_allegro5/example_allegro5.vcxproj.filters @@ -51,7 +51,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_allegro5/imconfig_allegro5.h b/examples/interactive/imgui-1.83/examples/example_allegro5/imconfig_allegro5.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_allegro5/imconfig_allegro5.h rename to examples/interactive/imgui-1.83/examples/example_allegro5/imconfig_allegro5.h diff --git a/examples/interactive/imgui-1.81/examples/example_allegro5/main.cpp b/examples/interactive/imgui-1.83/examples/example_allegro5/main.cpp similarity index 92% rename from examples/interactive/imgui-1.81/examples/example_allegro5/main.cpp rename to examples/interactive/imgui-1.83/examples/example_allegro5/main.cpp index e228e6ab..6306fdce 100644 --- a/examples/interactive/imgui-1.81/examples/example_allegro5/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_allegro5/main.cpp @@ -2,6 +2,14 @@ // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs +// On Windows, you can install Allegro5 using vcpkg: +// git clone https://github.com/Microsoft/vcpkg +// cd vcpkg +// bootstrap - vcpkg.bat +// vcpkg install allegro5 --triplet=x86-windows ; for win32 +// vcpkg install allegro5 --triplet=x64-windows ; for win64 +// vcpkg integrate install ; register include and libs in Visual Studio + #include #include #include @@ -122,7 +130,7 @@ int main(int, char**) // Rendering ImGui::Render(); - al_clear_to_color(al_map_rgba_f(clear_color.x, clear_color.y, clear_color.z, clear_color.w)); + al_clear_to_color(al_map_rgba_f(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w)); ImGui_ImplAllegro5_RenderDrawData(ImGui::GetDrawData()); al_flip_display(); } diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/CMakeLists.txt b/examples/interactive/imgui-1.83/examples/example_android_opengl3/CMakeLists.txt new file mode 100644 index 00000000..63531f4d --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.6) + +project(ImGuiExample) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_library(${CMAKE_PROJECT_NAME} SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../imgui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_demo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_draw.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_tables.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_widgets.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_android.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_opengl3.cpp + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c +) + +set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate" +) + +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + IMGUI_IMPL_OPENGL_ES3 +) + +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../backends + ${ANDROID_NDK}/sources/android/native_app_glue +) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE + android + EGL + GLESv3 + log +) diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/.gitignore b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/.gitignore new file mode 100644 index 00000000..3c7a6191 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/.gitignore @@ -0,0 +1,12 @@ +.cxx +.externalNativeBuild +build/ +*.iml + +.idea +.gradle +local.properties + +# Android Studio puts a Gradle wrapper here, that we don't want: +gradle/ +gradlew* diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/build.gradle b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/build.gradle new file mode 100644 index 00000000..aa7f0ead --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 29 + buildToolsVersion "30.0.3" + ndkVersion "21.4.7075529" + defaultConfig { + applicationId "imgui.example.android" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') + } + } + + externalNativeBuild { + cmake { + path "../../CMakeLists.txt" + } + } +} +repositories { + mavenCentral() +} +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c4009e52 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/java/MainActivity.kt b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/java/MainActivity.kt new file mode 100644 index 00000000..896a88c8 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/app/src/main/java/MainActivity.kt @@ -0,0 +1,40 @@ +package imgui.example.android + +import android.app.NativeActivity +import android.os.Bundle +import android.content.Context +import android.view.inputmethod.InputMethodManager +import android.view.KeyEvent +import java.util.concurrent.LinkedBlockingQueue + +class MainActivity : NativeActivity() { + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + fun showSoftInput() { + val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput(this.window.decorView, 0) + } + + fun hideSoftInput() { + val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(this.window.decorView.windowToken, 0) + } + + // Queue for the Unicode characters to be polled from native code (via pollUnicodeChar()) + private var unicodeCharacterQueue: LinkedBlockingQueue = LinkedBlockingQueue() + + // We assume dispatchKeyEvent() of the NativeActivity is actually called for every + // KeyEvent and not consumed by any View before it reaches here + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (event.action == KeyEvent.ACTION_DOWN) { + unicodeCharacterQueue.offer(event.getUnicodeChar(event.metaState)) + } + return super.dispatchKeyEvent(event) + } + + fun pollUnicodeChar(): Int { + return unicodeCharacterQueue.poll() ?: 0 + } +} diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/build.gradle b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/build.gradle new file mode 100644 index 00000000..59f9c78e --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/build.gradle @@ -0,0 +1,24 @@ +buildscript { + ext.kotlin_version = '1.4.31' + repositories { + google() + mavenCentral() + + } + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/settings.gradle b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/android/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/examples/interactive/imgui-1.83/examples/example_android_opengl3/main.cpp b/examples/interactive/imgui-1.83/examples/example_android_opengl3/main.cpp new file mode 100644 index 00000000..5ab69034 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_android_opengl3/main.cpp @@ -0,0 +1,369 @@ +// dear imgui: standalone example application for Android + OpenGL ES 3 +// If you are new to dear imgui, see examples/README.txt and documentation at the top of imgui.cpp. + +#include "imgui.h" +#include "imgui_impl_android.h" +#include "imgui_impl_opengl3.h" +#include +#include +#include +#include +#include + +// Data +static EGLDisplay g_EglDisplay = EGL_NO_DISPLAY; +static EGLSurface g_EglSurface = EGL_NO_SURFACE; +static EGLContext g_EglContext = EGL_NO_CONTEXT; +static struct android_app* g_App = NULL; +static bool g_Initialized = false; +static char g_LogTag[] = "ImGuiExample"; + +// Forward declarations of helper functions +static int ShowSoftKeyboardInput(); +static int PollUnicodeChars(); +static int GetAssetData(const char* filename, void** out_data); + +void init(struct android_app* app) +{ + if (g_Initialized) + return; + + g_App = app; + ANativeWindow_acquire(g_App->window); + + // Initialize EGL + // This is mostly boilerplate code for EGL... + { + g_EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (g_EglDisplay == EGL_NO_DISPLAY) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY"); + + if (eglInitialize(g_EglDisplay, 0, 0) != EGL_TRUE) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglInitialize() returned with an error"); + + const EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; + EGLint num_configs = 0; + if (eglChooseConfig(g_EglDisplay, egl_attributes, nullptr, 0, &num_configs) != EGL_TRUE) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig() returned with an error"); + if (num_configs == 0) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig() returned 0 matching config"); + + // Get the first matching config + EGLConfig egl_config; + eglChooseConfig(g_EglDisplay, egl_attributes, &egl_config, 1, &num_configs); + EGLint egl_format; + eglGetConfigAttrib(g_EglDisplay, egl_config, EGL_NATIVE_VISUAL_ID, &egl_format); + ANativeWindow_setBuffersGeometry(g_App->window, 0, 0, egl_format); + + const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + g_EglContext = eglCreateContext(g_EglDisplay, egl_config, EGL_NO_CONTEXT, egl_context_attributes); + + if (g_EglContext == EGL_NO_CONTEXT) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglCreateContext() returned EGL_NO_CONTEXT"); + + g_EglSurface = eglCreateWindowSurface(g_EglDisplay, egl_config, g_App->window, NULL); + eglMakeCurrent(g_EglDisplay, g_EglSurface, g_EglSurface, g_EglContext); + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + + // Disable loading/saving of .ini file from disk. + // FIXME: Consider using LoadIniSettingsFromMemory() / SaveIniSettingsToMemory() to save in appropriate location for Android. + io.IniFilename = NULL; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsClassic(); + + // Setup Platform/Renderer backends + ImGui_ImplAndroid_Init(g_App->window); + ImGui_ImplOpenGL3_Init("#version 300 es"); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Android: The TTF files have to be placed into the assets/ directory (android/app/src/main/assets), we use our GetAssetData() helper to retrieve them. + + // We load the default font with increased size to improve readability on many devices with "high" DPI. + // FIXME: Put some effort into DPI awareness. + // Important: when calling AddFontFromMemoryTTF(), ownership of font_data is transfered by Dear ImGui by default (deleted is handled by Dear ImGui), unless we set FontDataOwnedByAtlas=false in ImFontConfig + ImFontConfig font_cfg; + font_cfg.SizePixels = 22.0f; + io.Fonts->AddFontDefault(&font_cfg); + //void* font_data; + //int font_data_size; + //ImFont* font; + //font_data_size = GetAssetData("Roboto-Medium.ttf", &font_data); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); + //IM_ASSERT(font != NULL); + //font_data_size = GetAssetData("Cousine-Regular.ttf", &font_data); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 15.0f); + //IM_ASSERT(font != NULL); + //font_data_size = GetAssetData("DroidSans.ttf", &font_data); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); + //IM_ASSERT(font != NULL); + //font_data_size = GetAssetData("ProggyTiny.ttf", &font_data); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 10.0f); + //IM_ASSERT(font != NULL); + //font_data_size = GetAssetData("ArialUni.ttf", &font_data); + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != NULL); + + // Arbitrary scale-up + // FIXME: Put some effort into DPI awareness + ImGui::GetStyle().ScaleAllSizes(3.0f); + + g_Initialized = true; +} + +void tick() +{ + ImGuiIO& io = ImGui::GetIO(); + if (g_EglDisplay == EGL_NO_DISPLAY) + return; + + // Our state + 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); + + // Poll Unicode characters via JNI + // FIXME: do not call this every frame because of JNI overhead + PollUnicodeChars(); + + // Open on-screen (soft) input if requested by Dear ImGui + static bool WantTextInputLast = false; + if (io.WantTextInput && !WantTextInputLast) + ShowSoftKeyboardInput(); + WantTextInputLast = io.WantTextInput; + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplAndroid_NewFrame(); + ImGui::NewFrame(); + + // 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 created 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 / ImGui::GetIO().Framerate, ImGui::GetIO().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(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + eglSwapBuffers(g_EglDisplay, g_EglSurface); +} + +void shutdown() +{ + if (!g_Initialized) + return; + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplAndroid_Shutdown(); + ImGui::DestroyContext(); + + if (g_EglDisplay != EGL_NO_DISPLAY) + { + eglMakeCurrent(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (g_EglContext != EGL_NO_CONTEXT) + eglDestroyContext(g_EglDisplay, g_EglContext); + + if (g_EglSurface != EGL_NO_SURFACE) + eglDestroySurface(g_EglDisplay, g_EglSurface); + + eglTerminate(g_EglDisplay); + } + + g_EglDisplay = EGL_NO_DISPLAY; + g_EglContext = EGL_NO_CONTEXT; + g_EglSurface = EGL_NO_SURFACE; + ANativeWindow_release(g_App->window); + + g_Initialized = false; +} + +static void handleAppCmd(struct android_app* app, int32_t appCmd) +{ + switch (appCmd) + { + case APP_CMD_SAVE_STATE: + break; + case APP_CMD_INIT_WINDOW: + init(app); + break; + case APP_CMD_TERM_WINDOW: + shutdown(); + break; + case APP_CMD_GAINED_FOCUS: + break; + case APP_CMD_LOST_FOCUS: + break; + } +} + +static int32_t handleInputEvent(struct android_app* app, AInputEvent* inputEvent) +{ + return ImGui_ImplAndroid_HandleInputEvent(inputEvent); +} + +void android_main(struct android_app* app) +{ + app->onAppCmd = handleAppCmd; + app->onInputEvent = handleInputEvent; + + while (true) + { + int out_events; + struct android_poll_source* out_data; + + // Poll all events. If the app is not visible, this loop blocks until g_Initialized == true. + while (ALooper_pollAll(g_Initialized ? 0 : -1, NULL, &out_events, (void**)&out_data) >= 0) + { + // Process one event + if (out_data != NULL) + out_data->process(app, out_data); + + // Exit the app by returning from within the infinite loop + if (app->destroyRequested != 0) + { + // shutdown() should have been called already while processing the + // app command APP_CMD_TERM_WINDOW. But we play save here + if (!g_Initialized) + shutdown(); + + return; + } + } + + // Initiate a new frame + tick(); + } +} + +// Unfortunately, there is no way to show the on-screen input from native code. +// Therefore, we call ShowSoftKeyboardInput() of the main activity implemented in MainActivity.kt via JNI. +static int ShowSoftKeyboardInput() +{ + JavaVM* java_vm = g_App->activity->vm; + JNIEnv* java_env = NULL; + + jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6); + if (jni_return == JNI_ERR) + return -1; + + jni_return = java_vm->AttachCurrentThread(&java_env, NULL); + if (jni_return != JNI_OK) + return -2; + + jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); + if (native_activity_clazz == NULL) + return -3; + + jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V"); + if (method_id == NULL) + return -4; + + java_env->CallVoidMethod(g_App->activity->clazz, method_id); + + jni_return = java_vm->DetachCurrentThread(); + if (jni_return != JNI_OK) + return -5; + + return 0; +} + +// Unfortunately, the native KeyEvent implementation has no getUnicodeChar() function. +// Therefore, we implement the processing of KeyEvents in MainActivity.kt and poll +// the resulting Unicode characters here via JNI and send them to Dear ImGui. +static int PollUnicodeChars() +{ + JavaVM* java_vm = g_App->activity->vm; + JNIEnv* java_env = NULL; + + jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6); + if (jni_return == JNI_ERR) + return -1; + + jni_return = java_vm->AttachCurrentThread(&java_env, NULL); + if (jni_return != JNI_OK) + return -2; + + jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); + if (native_activity_clazz == NULL) + return -3; + + jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I"); + if (method_id == NULL) + return -4; + + // Send the actual characters to Dear ImGui + ImGuiIO& io = ImGui::GetIO(); + jint unicode_character; + while ((unicode_character = java_env->CallIntMethod(g_App->activity->clazz, method_id)) != 0) + io.AddInputCharacter(unicode_character); + + jni_return = java_vm->DetachCurrentThread(); + if (jni_return != JNI_OK) + return -5; + + return 0; +} + +// Helper to retrieve data placed into the assets/ directory (android/app/src/main/assets) +static int GetAssetData(const char* filename, void** outData) +{ + int num_bytes = 0; + AAsset* asset_descriptor = AAssetManager_open(g_App->activity->assetManager, filename, AASSET_MODE_BUFFER); + if (asset_descriptor) + { + num_bytes = AAsset_getLength(asset_descriptor); + *outData = IM_ALLOC(num_bytes); + int64_t num_bytes_read = AAsset_read(asset_descriptor, *outData, num_bytes); + AAsset_close(asset_descriptor); + IM_ASSERT(num_bytes_read == num_bytes); + } + return num_bytes; +} diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/README.md b/examples/interactive/imgui-1.83/examples/example_apple_metal/README.md similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/README.md rename to examples/interactive/imgui-1.83/examples/example_apple_metal/README.md diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj b/examples/interactive/imgui-1.83/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj rename to examples/interactive/imgui-1.83/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/iOS/Info-iOS.plist b/examples/interactive/imgui-1.83/examples/example_apple_metal/iOS/Info-iOS.plist similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/iOS/Info-iOS.plist rename to examples/interactive/imgui-1.83/examples/example_apple_metal/iOS/Info-iOS.plist diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/iOS/LaunchScreen.storyboard b/examples/interactive/imgui-1.83/examples/example_apple_metal/iOS/LaunchScreen.storyboard similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/iOS/LaunchScreen.storyboard rename to examples/interactive/imgui-1.83/examples/example_apple_metal/iOS/LaunchScreen.storyboard diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/macOS/Info-macOS.plist b/examples/interactive/imgui-1.83/examples/example_apple_metal/macOS/Info-macOS.plist similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/macOS/Info-macOS.plist rename to examples/interactive/imgui-1.83/examples/example_apple_metal/macOS/Info-macOS.plist diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/macOS/MainMenu.storyboard b/examples/interactive/imgui-1.83/examples/example_apple_metal/macOS/MainMenu.storyboard similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/macOS/MainMenu.storyboard rename to examples/interactive/imgui-1.83/examples/example_apple_metal/macOS/MainMenu.storyboard diff --git a/examples/interactive/imgui-1.81/examples/example_apple_metal/main.mm b/examples/interactive/imgui-1.83/examples/example_apple_metal/main.mm similarity index 55% rename from examples/interactive/imgui-1.81/examples/example_apple_metal/main.mm rename to examples/interactive/imgui-1.83/examples/example_apple_metal/main.mm index 015aee90..5d4b7710 100644 --- a/examples/interactive/imgui-1.81/examples/example_apple_metal/main.mm +++ b/examples/interactive/imgui-1.83/examples/example_apple_metal/main.mm @@ -15,26 +15,28 @@ #include "imgui.h" #include "imgui_impl_metal.h" - #if TARGET_OS_OSX #include "imgui_impl_osx.h" - -@interface ViewController : NSViewController +@interface AppViewController : NSViewController @end #else -@interface ViewController : UIViewController +@interface AppViewController : UIViewController @end #endif -@interface ViewController () +@interface AppViewController () @property (nonatomic, readonly) MTKView *mtkView; @property (nonatomic, strong) id device; @property (nonatomic, strong) id commandQueue; @end -@implementation ViewController +//----------------------------------------------------------------------------------- +// AppViewController +//----------------------------------------------------------------------------------- -- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil +@implementation AppViewController + +-(instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; @@ -80,17 +82,17 @@ - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullab return self; } -- (MTKView *)mtkView +-(MTKView *)mtkView { return (MTKView *)self.view; } -- (void)loadView +-(void)loadView { self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)]; } -- (void)viewDidLoad +-(void)viewDidLoad { [super viewDidLoad]; @@ -110,7 +112,7 @@ - (void)viewDidLoad // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our // window, we'd want to be much more careful than just ingesting the complete event stream. // To match the behavior of other backends, we pass every event down to the OS. - NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged | NSEventTypeScrollWheel; + NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged; [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) { ImGui_ImplOSX_HandleEvent(event, self.view); @@ -122,54 +124,121 @@ - (void)viewDidLoad #endif } -#pragma mark - Interaction +-(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); -- (void)mouseMoved:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + io.DeltaTime = 1 / float(view.preferredFramesPerSecond ?: 60); -- (void)mouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + id commandBuffer = [self.commandQueue commandBuffer]; -- (void)rightMouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; + if (renderPassDescriptor == nil) + { + [commandBuffer commit]; + return; + } -- (void)otherMouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + // Start the Dear ImGui frame + ImGui_ImplMetal_NewFrame(renderPassDescriptor); +#if TARGET_OS_OSX + ImGui_ImplOSX_NewFrame(view); +#endif + ImGui::NewFrame(); -- (void)mouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + // 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); -- (void)rightMouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + // 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); -- (void)otherMouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. + { + static float f = 0.0f; + static int counter = 0; -- (void)mouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. -- (void)rightMouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} + 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); -- (void)otherMouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().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_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; + + // Present + [commandBuffer presentDrawable:view.currentDrawable]; + [commandBuffer commit]; } -- (void)scrollWheel:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); +-(void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size +{ } +//----------------------------------------------------------------------------------- +// Input processing +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +// Forward Mouse/Keyboard events to Dear ImGui OSX backend. +// Other events are registered via addLocalMonitorForEventsMatchingMask() +-(void)mouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)rightMouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)otherMouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)mouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)rightMouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)otherMouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)mouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)mouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)rightMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)rightMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)otherMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)otherMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +-(void)scrollWheel:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } + #else // This touch mapping is super cheesy/hacky. We treat any touch on the screen @@ -177,7 +246,7 @@ - (void)scrollWheel:(NSEvent *)event { // 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 +-(void)updateIOWithTouchEvent:(UIEvent *)event { UITouch *anyTouch = event.allTouches.anyObject; CGPoint touchLocation = [anyTouch locationInView:self.view]; @@ -196,127 +265,18 @@ - (void)updateIOWithTouchEvent:(UIEvent *)event io.MouseDown[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]; -} +-(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 -#pragma mark - MTKViewDelegate - -- (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); - - io.DeltaTime = 1 / float(view.preferredFramesPerSecond ?: 60); - - id commandBuffer = [self.commandQueue commandBuffer]; - - // 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 float clear_color[4] = { 0.28f, 0.36f, 0.5f, 1.0f }; - - MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; - if (renderPassDescriptor != nil) - { - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); - - // Here, you could do additional rendering work, including other passes as necessary. - - id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - [renderEncoder pushDebugGroup:@"ImGui demo"]; - - // Start the Dear ImGui frame - ImGui_ImplMetal_NewFrame(renderPassDescriptor); -#if TARGET_OS_OSX - ImGui_ImplOSX_NewFrame(view); -#endif - ImGui::NewFrame(); - - // 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 created 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 / ImGui::GetIO().Framerate, ImGui::GetIO().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(); - ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); - - [renderEncoder popDebugGroup]; - [renderEncoder endEncoding]; - - [commandBuffer presentDrawable:view.currentDrawable]; - } - - [commandBuffer commit]; -} - -- (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size -{ -} - @end -#pragma mark - Application Delegate +//----------------------------------------------------------------------------------- +// AppDelegate +//----------------------------------------------------------------------------------- #if TARGET_OS_OSX @@ -326,11 +286,16 @@ @interface AppDelegate : NSObject @implementation AppDelegate -- (instancetype)init +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +-(instancetype)init { if (self = [super init]) { - NSViewController *rootViewController = [[ViewController alloc] initWithNibName:nil bundle:nil]; + NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil bundle:nil]; self.window = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable backing:NSBackingStoreBuffered @@ -343,11 +308,6 @@ - (instancetype)init return self; } -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender -{ - return YES; -} - @end #else @@ -358,10 +318,10 @@ @interface AppDelegate : UIResponder @implementation AppDelegate -- (BOOL)application:(UIApplication *)application +-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - UIViewController *rootViewController = [[ViewController alloc] init]; + UIViewController *rootViewController = [[AppViewController alloc] init]; self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; @@ -372,7 +332,9 @@ - (BOOL)application:(UIApplication *)application #endif -#pragma mark - main() +//----------------------------------------------------------------------------------- +// Application main() function +//----------------------------------------------------------------------------------- #if TARGET_OS_OSX diff --git a/examples/interactive/imgui-1.81/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj b/examples/interactive/imgui-1.83/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj rename to examples/interactive/imgui-1.83/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj diff --git a/examples/interactive/imgui-1.81/examples/example_apple_opengl2/main.mm b/examples/interactive/imgui-1.83/examples/example_apple_opengl2/main.mm similarity index 76% rename from examples/interactive/imgui-1.81/examples/example_apple_opengl2/main.mm rename to examples/interactive/imgui-1.83/examples/example_apple_opengl2/main.mm index d6d37e88..be33a6e8 100644 --- a/examples/interactive/imgui-1.81/examples/example_apple_opengl2/main.mm +++ b/examples/interactive/imgui-1.83/examples/example_apple_opengl2/main.mm @@ -2,30 +2,25 @@ // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs -#include "imgui.h" -#include "../../backends/imgui_impl_osx.h" -#include "../../backends/imgui_impl_opengl2.h" -#include #import #import #import +#include "imgui.h" +#include "imgui_impl_opengl2.h" +#include "imgui_impl_osx.h" + //----------------------------------------------------------------------------------- -// ImGuiExampleView +// AppView //----------------------------------------------------------------------------------- -@interface ImGuiExampleView : NSOpenGLView +@interface AppView : NSOpenGLView { NSTimer* animationTimer; } @end -@implementation ImGuiExampleView - --(void)animationTimerFired:(NSTimer*)timer -{ - [self setNeedsDisplay:YES]; -} +@implementation AppView -(void)prepareOpenGL { @@ -39,11 +34,55 @@ -(void)prepareOpenGL #endif } +-(void)initialize +{ + // Some events do not raise callbacks of AppView in some circumstances (for example when CMD key is held down). + // This monitor taps into global event stream and captures these events. + NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged; + [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) + { + ImGui_ImplOSX_HandleEvent(event, self); + return event; + }]; + + // 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 + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsClassic(); + + // Setup Platform/Renderer backends + ImGui_ImplOSX_Init(); + ImGui_ImplOpenGL2_Init(); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Read 'docs/FONTS.txt' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //io.Fonts->AddFontDefault(); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != NULL); +} + -(void)updateAndDrawDemoView { // Start the Dear ImGui frame - ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplOSX_NewFrame(self); + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplOSX_NewFrame(self); ImGui::NewFrame(); // Our state (make them static = more or less global) as a convenience to keep the example terse. @@ -88,18 +127,18 @@ -(void)updateAndDrawDemoView ImGui::End(); } - // Rendering - ImGui::Render(); - [[self openGLContext] makeCurrentContext]; - + // Rendering + ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); + + [[self openGLContext] makeCurrentContext]; GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); glViewport(0, 0, width, height); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL2_RenderDrawData(draw_data); + ImGui_ImplOpenGL2_RenderDrawData(draw_data); // Present [[self openGLContext] flushBuffer]; @@ -108,41 +147,20 @@ -(void)updateAndDrawDemoView animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.017 target:self selector:@selector(animationTimerFired:) userInfo:nil repeats:YES]; } --(void)reshape -{ - [[self openGLContext] update]; - [self updateAndDrawDemoView]; -} - --(void)drawRect:(NSRect)bounds -{ - [self updateAndDrawDemoView]; -} - --(BOOL)acceptsFirstResponder -{ - return (YES); -} +-(void)reshape { [[self openGLContext] update]; [self updateAndDrawDemoView]; } +-(void)drawRect:(NSRect)bounds { [self updateAndDrawDemoView]; } +-(void)animationTimerFired:(NSTimer*)timer { [self setNeedsDisplay:YES]; } +-(BOOL)acceptsFirstResponder { return (YES); } +-(BOOL)becomeFirstResponder { return (YES); } +-(BOOL)resignFirstResponder { return (YES); } +-(void)dealloc { animationTimer = nil; } --(BOOL)becomeFirstResponder -{ - return (YES); -} - --(BOOL)resignFirstResponder -{ - return (YES); -} - --(void)dealloc -{ - animationTimer = nil; -} +//----------------------------------------------------------------------------------- +// Input processing +//----------------------------------------------------------------------------------- // Forward Mouse/Keyboard events to Dear ImGui OSX backend. --(void)keyUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } --(void)keyDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } --(void)flagsChanged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } +// Other events are registered via addLocalMonitorForEventsMatchingMask() -(void)mouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)rightMouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)otherMouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } @@ -150,24 +168,24 @@ -(void)mouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, s -(void)rightMouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)otherMouseUp:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)mouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } --(void)rightMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } --(void)otherMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)mouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } +-(void)rightMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)rightMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } +-(void)otherMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)otherMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } -(void)scrollWheel:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self); } @end //----------------------------------------------------------------------------------- -// ImGuiExampleAppDelegate +// AppDelegate //----------------------------------------------------------------------------------- -@interface ImGuiExampleAppDelegate : NSObject +@interface AppDelegate : NSObject @property (nonatomic, readonly) NSWindow* window; @end -@implementation ImGuiExampleAppDelegate +@implementation AppDelegate @synthesize window = _window; -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication @@ -193,7 +211,7 @@ -(NSWindow*)window -(void)setupMenu { - NSMenu* mainMenuBar = [[NSMenu alloc] init]; + NSMenu* mainMenuBar = [[NSMenu alloc] init]; NSMenu* appMenu; NSMenuItem* menuItem; @@ -217,11 +235,11 @@ -(void)dealloc -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { - // Make the application a foreground application (else it won't receive keyboard events) - ProcessSerialNumber psn = {0, kCurrentProcess}; - TransformProcessType(&psn, kProcessTransformToForegroundApplication); + // Make the application a foreground application (else it won't receive keyboard events) + ProcessSerialNumber psn = {0, kCurrentProcess}; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); - // Menu + // Menu [self setupMenu]; NSOpenGLPixelFormatAttribute attrs[] = @@ -232,7 +250,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification }; NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - ImGuiExampleView* view = [[ImGuiExampleView alloc] initWithFrame:self.window.frame pixelFormat:format]; + AppView* view = [[AppView alloc] initWithFrame:self.window.frame pixelFormat:format]; format = nil; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) @@ -243,49 +261,23 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification if ([view openGLContext] == nil) NSLog(@"No OpenGL Context!"); - // 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 - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - //ImGui::StyleColorsClassic(); - - // Setup Platform/Renderer backends - ImGui_ImplOSX_Init(); - ImGui_ImplOpenGL2_Init(); - - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. - // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. - // - Read 'docs/FONTS.txt' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); - //IM_ASSERT(font != NULL); + [view initialize]; } @end +//----------------------------------------------------------------------------------- +// Application main() function +//----------------------------------------------------------------------------------- + int main(int argc, const char* argv[]) { - @autoreleasepool - { - NSApp = [NSApplication sharedApplication]; - ImGuiExampleAppDelegate* delegate = [[ImGuiExampleAppDelegate alloc] init]; - [[NSApplication sharedApplication] setDelegate:delegate]; - [NSApp run]; - } - return NSApplicationMain(argc, argv); + @autoreleasepool + { + NSApp = [NSApplication sharedApplication]; + AppDelegate* delegate = [[AppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate:delegate]; + [NSApp run]; + } + return NSApplicationMain(argc, argv); } diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/Makefile b/examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/Makefile similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/Makefile rename to examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/Makefile diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/README.md b/examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/README.md similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/README.md rename to examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/README.md diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/main.cpp b/examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/main.cpp similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/main.cpp rename to examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/main.cpp index 66e0cb3f..9e1b37b3 100644 --- a/examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/main.cpp @@ -124,7 +124,7 @@ static void main_loop(void* arg) // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(g_Window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). @@ -168,7 +168,7 @@ static void main_loop(void* arg) ImGui::Render(); SDL_GL_MakeCurrent(g_Window, g_GLContext); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(g_Window); diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/shell_minimal.html b/examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/shell_minimal.html similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_emscripten_opengl3/shell_minimal.html rename to examples/interactive/imgui-1.83/examples/example_emscripten_opengl3/shell_minimal.html diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/Makefile b/examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/Makefile similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/Makefile rename to examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/Makefile diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/README.md b/examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/README.md similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/README.md rename to examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/README.md diff --git a/examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/main.cpp b/examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/main.cpp similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/main.cpp rename to examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/main.cpp index 9b72ba9b..d11d906c 100644 --- a/examples/interactive/imgui-1.81/examples/example_emscripten_wgpu/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_emscripten_wgpu/main.cpp @@ -147,7 +147,7 @@ static void main_loop(void* window) wgpu_swap_chain_height = height; WGPUSwapChainDescriptor swap_chain_desc = {}; - swap_chain_desc.usage = WGPUTextureUsage_OutputAttachment; + swap_chain_desc.usage = WGPUTextureUsage_RenderAttachment; swap_chain_desc.format = WGPUTextureFormat_RGBA8Unorm; swap_chain_desc.width = width; swap_chain_desc.height = height; @@ -202,11 +202,11 @@ static void main_loop(void* window) // Rendering ImGui::Render(); - WGPURenderPassColorAttachmentDescriptor color_attachments = {}; + WGPURenderPassColorAttachment color_attachments = {}; color_attachments.loadOp = WGPULoadOp_Clear; color_attachments.storeOp = WGPUStoreOp_Store; - color_attachments.clearColor = { clear_color.x, clear_color.y, clear_color.z, clear_color.w }; - color_attachments.attachment = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); + color_attachments.clearColor = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; + color_attachments.view = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); WGPURenderPassDescriptor render_pass_desc = {}; render_pass_desc.colorAttachmentCount = 1; render_pass_desc.colorAttachments = &color_attachments; @@ -221,7 +221,7 @@ static void main_loop(void* window) WGPUCommandBufferDescriptor cmd_buffer_desc = {}; WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); - WGPUQueue queue = wgpuDeviceGetDefaultQueue(wgpu_device); + WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); wgpuQueueSubmit(queue, 1, &cmd_buffer); } diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_metal/Makefile b/examples/interactive/imgui-1.83/examples/example_glfw_metal/Makefile similarity index 92% rename from examples/interactive/imgui-1.81/examples/example_glfw_metal/Makefile rename to examples/interactive/imgui-1.83/examples/example_glfw_metal/Makefile index 8f08b965..a174bc8f 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_metal/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_glfw_metal/Makefile @@ -14,9 +14,10 @@ SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) LIBS = -framework Metal -framework MetalKit -framework Cocoa -framework IOKit -framework CoreVideo -framework QuartzCore -LIBS += -L/usr/local/lib -lglfw +LIBS += -L/usr/local/lib -L/opt/homebrew/lib +LIBS += -lglfw -CXXFLAGS = -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I/usr/local/include +CXXFLAGS = -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I/usr/local/include -I/opt/homebrew/include CXXFLAGS += -Wall -Wformat CFLAGS = $(CXXFLAGS) diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_metal/main.mm b/examples/interactive/imgui-1.83/examples/example_glfw_metal/main.mm similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_glfw_metal/main.mm rename to examples/interactive/imgui-1.83/examples/example_glfw_metal/main.mm index d818dba0..2ef6acdf 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_metal/main.mm +++ b/examples/interactive/imgui-1.83/examples/example_glfw_metal/main.mm @@ -99,7 +99,7 @@ int main(int, char**) id drawable = [layer nextDrawable]; id commandBuffer = [commandQueue commandBuffer]; - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); renderPassDescriptor.colorAttachments[0].texture = drawable.texture; renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/Makefile b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/Makefile similarity index 92% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl2/Makefile rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl2/Makefile index a24a91f3..5c19a27f 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/Makefile @@ -41,15 +41,15 @@ endif ifeq ($(UNAME_S), Darwin) #APPLE ECHO_MESSAGE = "Mac OS X" LIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo - LIBS += -L/usr/local/lib -L/opt/local/lib + LIBS += -L/usr/local/lib -L/opt/local/lib -L/opt/homebrew/lib #LIBS += -lglfw3 LIBS += -lglfw - CXXFLAGS += -I/usr/local/include -I/opt/local/include + CXXFLAGS += -I/usr/local/include -I/opt/local/include -I/opt/homebrew/include CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) +ifeq ($(OS), Windows_NT) ECHO_MESSAGE = "MinGW" LIBS += -lglfw3 -lgdi32 -lopengl32 -limm32 diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl2/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl2/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj index 2322ce25..faf6d9a5 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -172,10 +172,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters index 8327557c..69b285d1 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters @@ -54,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/main.cpp b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/main.cpp similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl2/main.cpp rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl2/main.cpp index 732ceaed..8cc32328 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl2/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl2/main.cpp @@ -144,7 +144,7 @@ int main(int, char**) int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!), diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/Makefile b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/Makefile similarity index 83% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl3/Makefile rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl3/Makefile index 294d103d..15eb72a2 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/Makefile @@ -21,15 +21,19 @@ SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) +LINUX_GL_LIBS = -lGL CXXFLAGS = -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends CXXFLAGS += -g -Wall -Wformat LIBS = ##--------------------------------------------------------------------- -## OPENGL LOADER +## OPENGL LOADER / OPENGL ES ##--------------------------------------------------------------------- +## See below for OpenGL ES option (no loader required) - comment out +## the following if you want to use OpenGL ES instead of Desktop GL. + ## Using OpenGL loader: gl3w [default] SOURCES += ../libs/gl3w/GL/gl3w.c CXXFLAGS += -I../libs/gl3w -DIMGUI_IMPL_OPENGL_LOADER_GL3W @@ -56,13 +60,18 @@ CXXFLAGS += -I../libs/gl3w -DIMGUI_IMPL_OPENGL_LOADER_GL3W # CXXFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GLBINDING2 # LIBS += -lglbinding +## Using OpenGL ES, no loader required +## This assumes a GL ES library available in the system, e.g. libGLESv2.so +# CXXFLAGS += -DIMGUI_IMPL_OPENGL_ES2 +# LINUX_GL_LIBS = -lGLESv2 + ##--------------------------------------------------------------------- ## BUILD FLAGS PER PLATFORM ##--------------------------------------------------------------------- ifeq ($(UNAME_S), Linux) #LINUX ECHO_MESSAGE = "Linux" - LIBS += -lGL `pkg-config --static --libs glfw3` + LIBS += $(LINUX_GL_LIBS) `pkg-config --static --libs glfw3` CXXFLAGS += `pkg-config --cflags glfw3` CFLAGS = $(CXXFLAGS) @@ -71,15 +80,15 @@ endif ifeq ($(UNAME_S), Darwin) #APPLE ECHO_MESSAGE = "Mac OS X" LIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo - LIBS += -L/usr/local/lib -L/opt/local/lib + LIBS += -L/usr/local/lib -L/opt/local/lib -L/opt/homebrew/lib #LIBS += -lglfw3 LIBS += -lglfw - CXXFLAGS += -I/usr/local/include -I/opt/local/include + CXXFLAGS += -I/usr/local/include -I/opt/local/include -I/opt/homebrew/include CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) +ifeq ($(OS), Windows_NT) ECHO_MESSAGE = "MinGW" LIBS += -lglfw3 -lgdi32 -lopengl32 -limm32 diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl3/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl3/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj index 61184d87..682db28b 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -175,10 +175,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters index 6e3859c1..586ad0af 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters @@ -66,7 +66,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/main.cpp b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/main.cpp similarity index 95% rename from examples/interactive/imgui-1.81/examples/example_glfw_opengl3/main.cpp rename to examples/interactive/imgui-1.83/examples/example_glfw_opengl3/main.cpp index 6fa93d13..edccc4e2 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_opengl3/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_glfw_opengl3/main.cpp @@ -8,11 +8,13 @@ #include "imgui_impl_opengl3.h" #include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. -#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Initialize with gl3wInit() #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Initialize with glewInit() @@ -57,7 +59,13 @@ int main(int, char**) return 1; // Decide GL+GLSL versions -#ifdef __APPLE__ +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#elif defined(__APPLE__) // GL 3.2 + GLSL 150 const char* glsl_version = "#version 150"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); @@ -208,7 +216,7 @@ int main(int, char**) int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/CMakeLists.txt b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/CMakeLists.txt similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/CMakeLists.txt rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/CMakeLists.txt diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/build_win64.bat b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/build_win64.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/build_win64.bat rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/build_win64.bat diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj index 1667b5ab..34b3f21c 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -172,10 +172,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters index 943eb3dd..adc3365c 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters @@ -54,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/main.cpp b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/main.cpp similarity index 95% rename from examples/interactive/imgui-1.81/examples/example_glfw_vulkan/main.cpp rename to examples/interactive/imgui-1.83/examples/example_glfw_vulkan/main.cpp index baad2195..4e7af688 100644 --- a/examples/interactive/imgui-1.81/examples/example_glfw_vulkan/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_glfw_vulkan/main.cpp @@ -122,10 +122,22 @@ static void SetupVulkan(const char** extensions, uint32_t extensions_count) err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, gpus); check_vk_result(err); - // If a number >1 of GPUs got reported, you should find the best fit GPU for your purpose - // e.g. VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU if available, or with the greatest memory available, etc. - // for sake of simplicity we'll just take the first one, assuming it has a graphics queue family. - g_PhysicalDevice = gpus[0]; + // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers + // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple + // dedicated GPUs) is out of scope of this sample. + int use_gpu = 0; + for (int i = 0; i < (int)gpu_count; i++) + { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(gpus[i], &properties); + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + { + use_gpu = i; + break; + } + } + + g_PhysicalDevice = gpus[use_gpu]; free(gpus); } @@ -253,7 +265,7 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) VkSemaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; err = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, &wd->FrameIndex); - if (err == VK_ERROR_OUT_OF_DATE_KHR) + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { g_SwapChainRebuild = true; return; @@ -326,7 +338,7 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) info.pSwapchains = &wd->Swapchain; info.pImageIndices = &wd->FrameIndex; VkResult err = vkQueuePresentKHR(g_Queue, &info); - if (err == VK_ERROR_OUT_OF_DATE_KHR) + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { g_SwapChainRebuild = true; return; @@ -530,7 +542,10 @@ int main(int, char**) ImGui::Render(); ImDrawData* main_draw_data = ImGui::GetDrawData(); const bool main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0f || main_draw_data->DisplaySize.y <= 0.0f); - memcpy(&wd->ClearValue.color.float32[0], &clear_color, 4 * sizeof(float)); + wd->ClearValue.color.float32[0] = clear_color.x * clear_color.w; + wd->ClearValue.color.float32[1] = clear_color.y * clear_color.w; + wd->ClearValue.color.float32[2] = clear_color.z * clear_color.w; + wd->ClearValue.color.float32[3] = clear_color.w; if (!main_is_minimized) FrameRender(wd, main_draw_data); diff --git a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/Makefile b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/Makefile similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_glut_opengl2/Makefile rename to examples/interactive/imgui-1.83/examples/example_glut_opengl2/Makefile index 952ca318..a980f56f 100644 --- a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/Makefile @@ -40,7 +40,7 @@ ifeq ($(UNAME_S), Darwin) #APPLE CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) +ifeq ($(OS), Windows_NT) ECHO_MESSAGE = "MinGW" LIBS += -lgdi32 -lopengl32 -limm32 ifeq ($(shell pkg-config freeglut --exists 2> /dev/null && echo yes || echo no),yes) diff --git a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj similarity index 90% rename from examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj rename to examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj index f14ea156..4c9d00f5 100644 --- a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -90,7 +90,7 @@ Level4 Disabled - $(GLUT_INCLUDE_DIR);..\..;%(AdditionalIncludeDirectories) + $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) true @@ -104,7 +104,7 @@ Level4 Disabled - $(GLUT_INCLUDE_DIR);..\..;%(AdditionalIncludeDirectories) + $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) true @@ -120,7 +120,7 @@ MaxSpeed true true - $(GLUT_INCLUDE_DIR);..\..;%(AdditionalIncludeDirectories) + $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) false @@ -140,7 +140,7 @@ MaxSpeed true true - $(GLUT_INCLUDE_DIR);..\..;%(AdditionalIncludeDirectories) + $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) false @@ -172,10 +172,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters index 69882910..3c017ba9 100644 --- a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters @@ -54,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/main.cpp b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/main.cpp similarity index 93% rename from examples/interactive/imgui-1.81/examples/example_glut_opengl2/main.cpp rename to examples/interactive/imgui-1.83/examples/example_glut_opengl2/main.cpp index e61e2b21..faf55123 100644 --- a/examples/interactive/imgui-1.81/examples/example_glut_opengl2/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_glut_opengl2/main.cpp @@ -6,6 +6,14 @@ // !!! If someone or something is teaching you GLUT today, you are being abused. Please show some resistance. !!! // !!! Nowadays, prefer using GLFW or SDL instead! +// On Windows, you can install Freeglut using vcpkg: +// git clone https://github.com/Microsoft/vcpkg +// cd vcpkg +// bootstrap - vcpkg.bat +// vcpkg install freeglut --triplet=x86-windows ; for win32 +// vcpkg install freeglut --triplet=x64-windows ; for win64 +// vcpkg integrate install ; register include and libs in Visual Studio + #include "imgui.h" #include "imgui_impl_glut.h" #include "imgui_impl_opengl2.h" @@ -76,7 +84,7 @@ void glut_display_func() ImGui::Render(); ImGuiIO& io = ImGui::GetIO(); glViewport(0, 0, (GLsizei)io.DisplaySize.x, (GLsizei)io.DisplaySize.y); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound, but prefer using the GL3+ code. ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); diff --git a/examples/interactive/imgui-1.81/examples/example_marmalade/data/app.icf b/examples/interactive/imgui-1.83/examples/example_marmalade/data/app.icf similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_marmalade/data/app.icf rename to examples/interactive/imgui-1.83/examples/example_marmalade/data/app.icf diff --git a/examples/interactive/imgui-1.81/examples/example_marmalade/main.cpp b/examples/interactive/imgui-1.83/examples/example_marmalade/main.cpp similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_marmalade/main.cpp rename to examples/interactive/imgui-1.83/examples/example_marmalade/main.cpp diff --git a/examples/interactive/imgui-1.81/examples/example_marmalade/marmalade_example.mkb b/examples/interactive/imgui-1.83/examples/example_marmalade/marmalade_example.mkb similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_marmalade/marmalade_example.mkb rename to examples/interactive/imgui-1.83/examples/example_marmalade/marmalade_example.mkb diff --git a/examples/interactive/imgui-1.81/examples/example_null/Makefile b/examples/interactive/imgui-1.83/examples/example_null/Makefile similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_null/Makefile rename to examples/interactive/imgui-1.83/examples/example_null/Makefile index edf0145e..2197c01c 100644 --- a/examples/interactive/imgui-1.81/examples/example_null/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_null/Makefile @@ -57,11 +57,12 @@ ifeq ($(UNAME_S), Darwin) #APPLE CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) +ifeq ($(OS), Windows_NT) ECHO_MESSAGE = "MinGW" ifneq ($(WITH_EXTRA_WARNINGS), 0) CXXFLAGS += -Wextra -Wpedantic endif + LIBS += -limm32 CFLAGS = $(CXXFLAGS) endif diff --git a/examples/interactive/imgui-1.81/examples/example_null/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_null/build_win32.bat similarity index 76% rename from examples/interactive/imgui-1.81/examples/example_null/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_null/build_win32.bat index 38e79148..0cdfdc93 100644 --- a/examples/interactive/imgui-1.81/examples/example_null/build_win32.bat +++ b/examples/interactive/imgui-1.83/examples/example_null/build_win32.bat @@ -1,3 +1,3 @@ @REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler. mkdir Debug -cl /nologo /Zi /MD /I ..\.. %* *.cpp ..\..\*.cpp /FeDebug/example_null.exe /FoDebug/ /link gdi32.lib shell32.lib +cl /nologo /Zi /MD /I ..\.. %* *.cpp ..\..\*.cpp /FeDebug/example_null.exe /FoDebug/ /link gdi32.lib shell32.lib imm32.lib diff --git a/examples/interactive/imgui-1.81/examples/example_null/main.cpp b/examples/interactive/imgui-1.83/examples/example_null/main.cpp similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_null/main.cpp rename to examples/interactive/imgui-1.83/examples/example_null/main.cpp diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/build_win32.bat similarity index 90% rename from examples/interactive/imgui-1.81/examples/example_sdl_directx11/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_sdl_directx11/build_win32.bat index 7433c74f..b9308270 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/build_win32.bat +++ b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/build_win32.bat @@ -3,6 +3,6 @@ @set OUT_EXE=example_sdl_directx11 @set INCLUDES=/I..\.. /I..\..\backends /I%SDL2_DIR%\include /I "%WindowsSdkDir%Include\um" /I "%WindowsSdkDir%Include\shared" /I "%DXSDK_DIR%Include" @set SOURCES=main.cpp ..\..\backends\imgui_impl_sdl.cpp ..\..\backends\imgui_impl_dx11.cpp ..\..\imgui*.cpp -@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib /LIBPATH:"%DXSDK_DIR%/Lib/x86" d3d11.lib d3dcompiler.lib +@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib /LIBPATH:"%DXSDK_DIR%/Lib/x86" d3d11.lib d3dcompiler.lib shell32.lib mkdir %OUT_DIR% cl /nologo /Zi /MD %INCLUDES% %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% /subsystem:console diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj similarity index 91% rename from examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj rename to examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj index 99dd54af..ac636d2f 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -29,27 +29,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -91,7 +91,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -105,7 +105,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -121,7 +121,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -141,7 +141,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -173,10 +173,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters index 8ebfd6d1..15fc8533 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters @@ -53,7 +53,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/main.cpp b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/main.cpp similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_sdl_directx11/main.cpp rename to examples/interactive/imgui-1.83/examples/example_sdl_directx11/main.cpp index 11dc8a0a..ac45b32d 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_directx11/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_sdl_directx11/main.cpp @@ -115,7 +115,7 @@ int main(int, char**) if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) done = true; if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED && event.window.windowID == SDL_GetWindowID(window)) - { + { // Release all outstanding references to the swap chain's buffers before resizing. CleanupRenderTarget(); g_pSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0); @@ -125,7 +125,7 @@ int main(int, char**) // Start the Dear ImGui frame ImGui_ImplDX11_NewFrame(); - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). @@ -167,8 +167,9 @@ int main(int, char**) // Rendering ImGui::Render(); + const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL); - g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, (float*)&clear_color); + g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); // Update and Render additional Platform Windows diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_metal/Makefile b/examples/interactive/imgui-1.83/examples/example_sdl_metal/Makefile similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_sdl_metal/Makefile rename to examples/interactive/imgui-1.83/examples/example_sdl_metal/Makefile diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_metal/main.mm b/examples/interactive/imgui-1.83/examples/example_sdl_metal/main.mm similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_sdl_metal/main.mm rename to examples/interactive/imgui-1.83/examples/example_sdl_metal/main.mm index 1f33503d..caa86e64 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_metal/main.mm +++ b/examples/interactive/imgui-1.83/examples/example_sdl_metal/main.mm @@ -107,7 +107,7 @@ int main(int, char**) id drawable = [layer nextDrawable]; id commandBuffer = [commandQueue commandBuffer]; - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); renderPassDescriptor.colorAttachments[0].texture = drawable.texture; renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; @@ -116,7 +116,7 @@ int main(int, char**) // Start the Dear ImGui frame ImGui_ImplMetal_NewFrame(renderPassDescriptor); - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/Makefile b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/Makefile similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/Makefile rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/Makefile index 5fdad0ba..92e0554b 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/Makefile @@ -48,7 +48,7 @@ ifeq ($(UNAME_S), Darwin) #APPLE CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) +ifeq ($(OS), Windows_NT) ECHO_MESSAGE = "MinGW" LIBS += -lgdi32 -lopengl32 -limm32 `pkg-config --static --libs sdl2` diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/README.md b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/README.md similarity index 64% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/README.md rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/README.md index 00900638..2bf4d03a 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/README.md +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/README.md @@ -1,6 +1,10 @@ # How to Build +- On Windows with Visual Studio's IDE + +Use the provided project file (.vcxproj). Add to solution (imgui_examples.sln) if necessary. + - On Windows with Visual Studio's CLI ``` @@ -14,12 +18,12 @@ cl /Zi /MD /I.. /I..\.. /I%SDL2_DIR%\include main.cpp ..\..\backends\imgui_impl_ - On Linux and similar Unixes ``` -c++ `sdl2-config --cflags` -I .. -I ../.. main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl2.cpp ../../imgui*.cpp `sdl2-config --libs` -lGL +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl2.cpp ../../imgui*.cpp `sdl2-config --libs` -lGL ``` - On Mac OS X ``` brew install sdl2 -c++ `sdl2-config --cflags` -I .. -I ../.. main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl2.cpp ../../imgui*.cpp `sdl2-config --libs` -framework OpenGl +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl2.cpp ../../imgui*.cpp `sdl2-config --libs` -framework OpenGl ``` diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/build_win32.bat similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/build_win32.bat index d7fb003b..47529e28 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/build_win32.bat +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/build_win32.bat @@ -3,6 +3,6 @@ @set OUT_EXE=example_sdl_opengl2 @set INCLUDES=/I..\.. /I..\..\backends /I%SDL2_DIR%\include @set SOURCES=main.cpp ..\..\backends\imgui_impl_sdl.cpp ..\..\backends\imgui_impl_opengl2.cpp ..\..\imgui*.cpp -@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib opengl32.lib +@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib opengl32.lib shell32.lib mkdir %OUT_DIR% cl /nologo /Zi /MD %INCLUDES% %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% /subsystem:console diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj similarity index 91% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj index 6b9e642d..d22a67ba 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -90,7 +90,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -104,7 +104,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -120,7 +120,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -140,7 +140,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -172,10 +172,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters index 643b0ed7..2253b865 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters @@ -54,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/main.cpp b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/main.cpp similarity index 98% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl2/main.cpp rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl2/main.cpp index 388512cc..d3aaa683 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl2/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl2/main.cpp @@ -106,7 +106,7 @@ int main(int, char**) // Start the Dear ImGui frame ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). @@ -149,7 +149,7 @@ int main(int, char**) // Rendering ImGui::Render(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/Makefile b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/Makefile similarity index 79% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/Makefile rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/Makefile index 6e30a135..1a278556 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/Makefile +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/Makefile @@ -21,15 +21,19 @@ SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) +LINUX_GL_LIBS = -lGL CXXFLAGS = -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends CXXFLAGS += -g -Wall -Wformat LIBS = ##--------------------------------------------------------------------- -## OPENGL LOADER +## OPENGL LOADER / OPENGL ES ##--------------------------------------------------------------------- +## See below for OpenGL ES option (no loader required) - comment out +## the following if you want to use OpenGL ES instead of Desktop GL. + ## Using OpenGL loader: gl3w [default] SOURCES += ../libs/gl3w/GL/gl3w.c CXXFLAGS += -I../libs/gl3w -DIMGUI_IMPL_OPENGL_LOADER_GL3W @@ -56,13 +60,21 @@ CXXFLAGS += -I../libs/gl3w -DIMGUI_IMPL_OPENGL_LOADER_GL3W # CXXFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GLBINDING2 # LIBS += -lglbinding +## Using OpenGL ES, no loader required +## This assumes a GL ES library available in the system, e.g. libGLESv2.so +# CXXFLAGS += -DIMGUI_IMPL_OPENGL_ES2 +# LINUX_GL_LIBS = -lGLESv2 +## If you're on a Raspberry Pi and want to use the legacy drivers, +## use the following instead: +# LINUX_GL_LIBS = -L/opt/vc/lib -lbrcmGLESv2 + ##--------------------------------------------------------------------- ## BUILD FLAGS PER PLATFORM ##--------------------------------------------------------------------- ifeq ($(UNAME_S), Linux) #LINUX ECHO_MESSAGE = "Linux" - LIBS += -lGL -ldl `sdl2-config --libs` + LIBS += $(LINUX_GL_LIBS) -ldl `sdl2-config --libs` CXXFLAGS += `sdl2-config --cflags` CFLAGS = $(CXXFLAGS) @@ -78,12 +90,12 @@ ifeq ($(UNAME_S), Darwin) #APPLE CFLAGS = $(CXXFLAGS) endif -ifeq ($(findstring MINGW,$(UNAME_S)),MINGW) - ECHO_MESSAGE = "MinGW" - LIBS += -lgdi32 -lopengl32 -limm32 `pkg-config --static --libs sdl2` +ifeq ($(OS), Windows_NT) + ECHO_MESSAGE = "MinGW" + LIBS += -lgdi32 -lopengl32 -limm32 `pkg-config --static --libs sdl2` - CXXFLAGS += `pkg-config --cflags sdl2` - CFLAGS = $(CXXFLAGS) + CXXFLAGS += `pkg-config --cflags sdl2` + CFLAGS = $(CXXFLAGS) endif ##--------------------------------------------------------------------- diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/README.md b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/README.md similarity index 62% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/README.md rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/README.md index edeafec3..9532dcf0 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/README.md +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/README.md @@ -1,6 +1,10 @@ # How to Build +- On Windows with Visual Studio's IDE + +Use the provided project file (.vcxproj). Add to solution (imgui_examples.sln) if necessary. + - On Windows with Visual Studio's CLI ``` @@ -14,12 +18,12 @@ cl /Zi /MD /I.. /I..\.. /I%SDL2_DIR%\include /I..\libs\gl3w main.cpp ..\..\backe - On Linux and similar Unixes ``` -c++ `sdl2-config --cflags` -I .. -I ../.. -I ../libs/gl3w main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl3.cpp ../../imgui*.cpp ../libs/gl3w/GL/gl3w.c `sdl2-config --libs` -lGL -ldl +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends -I ../libs/gl3w main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl3.cpp ../../imgui*.cpp ../libs/gl3w/GL/gl3w.c `sdl2-config --libs` -lGL -ldl ``` - On Mac OS X ``` brew install sdl2 -c++ `sdl2-config --cflags` -I .. -I ../.. -I ../libs/gl3w main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl3.cpp ../../imgui*.cpp ../libs/gl3w/GL/gl3w.c `sdl2-config --libs` -framework OpenGl -framework CoreFoundation +c++ `sdl2-config --cflags` -I .. -I ../.. -I ../../backends -I ../libs/gl3w main.cpp ../../backends/imgui_impl_sdl.cpp ../../backends/imgui_impl_opengl3.cpp ../../imgui*.cpp ../libs/gl3w/GL/gl3w.c `sdl2-config --libs` -framework OpenGl -framework CoreFoundation ``` diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/build_win32.bat similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/build_win32.bat index 8f549601..72e89b19 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/build_win32.bat +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/build_win32.bat @@ -3,6 +3,6 @@ @set OUT_EXE=example_sdl_opengl3 @set INCLUDES=/I..\.. /I..\..\backends /I%SDL2_DIR%\include /I..\libs\gl3w @set SOURCES=main.cpp ..\..\backends\imgui_impl_sdl.cpp ..\..\backends\imgui_impl_opengl3.cpp ..\..\imgui*.cpp ..\libs\gl3w\GL\gl3w.c -@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib opengl32.lib +@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 SDL2.lib SDL2main.lib opengl32.lib shell32.lib mkdir %OUT_DIR% cl /nologo /Zi /MD %INCLUDES% %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% /subsystem:console diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj similarity index 91% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj index 7ef1a792..2db7a080 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -90,7 +90,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;..\libs\gl3w;%(AdditionalIncludeDirectories) true @@ -104,7 +104,7 @@ Level4 Disabled - ..\..;..\..\backends;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;..\libs\gl3w;%(AdditionalIncludeDirectories) true @@ -120,7 +120,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;..\libs\gl3w;%(AdditionalIncludeDirectories) false @@ -140,7 +140,7 @@ MaxSpeed true true - ..\..;..\..\backends;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;..\libs\gl3w;%(AdditionalIncludeDirectories) false @@ -175,10 +175,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters index f6e323de..de967f4e 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters @@ -66,7 +66,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/main.cpp b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/main.cpp similarity index 94% rename from examples/interactive/imgui-1.81/examples/example_sdl_opengl3/main.cpp rename to examples/interactive/imgui-1.83/examples/example_sdl_opengl3/main.cpp index 4b1896ac..334a48b8 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_opengl3/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_sdl_opengl3/main.cpp @@ -10,11 +10,13 @@ #include #include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. -#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Initialize with gl3wInit() #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Initialize with glewInit() @@ -49,7 +51,14 @@ int main(int, char**) } // Decide GL+GLSL versions -#ifdef __APPLE__ +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) // GL 3.2 Core + GLSL 150 const char* glsl_version = "#version 150"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac @@ -167,7 +176,7 @@ int main(int, char**) // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). @@ -210,7 +219,7 @@ int main(int, char**) // Rendering ImGui::Render(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/build_win32.bat new file mode 100644 index 00000000..21b5ea31 --- /dev/null +++ b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/build_win32.bat @@ -0,0 +1,10 @@ +@REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler. + +@set OUT_EXE=example_sdl_vulkan +@set INCLUDES=/I..\.. /I..\..\backends /I%SDL2_DIR%\include /I %VULKAN_SDK%\include +@set SOURCES=main.cpp ..\..\backends\imgui_impl_sdl.cpp ..\..\backends\imgui_impl_vulkan.cpp ..\..\imgui*.cpp +@set LIBS=/LIBPATH:%SDL2_DIR%\lib\x86 /libpath:%VULKAN_SDK%\lib32 SDL2.lib SDL2main.lib shell32.lib vulkan-1.lib + +@set OUT_DIR=Debug +mkdir %OUT_DIR% +cl /nologo /Zi /MD %INCLUDES% %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% /subsystem:console diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj similarity index 90% rename from examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj rename to examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj index 8567b790..5cc3eab2 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true MultiByte - v110 + v140 Application true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 Application false true MultiByte - v110 + v140 @@ -90,7 +90,7 @@ Level4 Disabled - ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -104,7 +104,7 @@ Level4 Disabled - ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) true @@ -120,7 +120,7 @@ MaxSpeed true true - ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -140,7 +140,7 @@ MaxSpeed true true - ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) + ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) false @@ -172,10 +172,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters similarity index 92% rename from examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters index 8a6b48e3..063a5936 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj.filters @@ -31,6 +31,9 @@ imgui + + sources + @@ -51,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/main.cpp b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/main.cpp similarity index 95% rename from examples/interactive/imgui-1.81/examples/example_sdl_vulkan/main.cpp rename to examples/interactive/imgui-1.83/examples/example_sdl_vulkan/main.cpp index 59827fb0..e3730ebf 100644 --- a/examples/interactive/imgui-1.81/examples/example_sdl_vulkan/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_sdl_vulkan/main.cpp @@ -114,10 +114,22 @@ static void SetupVulkan(const char** extensions, uint32_t extensions_count) err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, gpus); check_vk_result(err); - // If a number >1 of GPUs got reported, you should find the best fit GPU for your purpose - // e.g. VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU if available, or with the greatest memory available, etc. - // for sake of simplicity we'll just take the first one, assuming it has a graphics queue family. - g_PhysicalDevice = gpus[0]; + // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers + // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple + // dedicated GPUs) is out of scope of this sample. + int use_gpu = 0; + for (int i = 0; i < (int)gpu_count; i++) + { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(gpus[i], &properties); + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + { + use_gpu = i; + break; + } + } + + g_PhysicalDevice = gpus[use_gpu]; free(gpus); } @@ -245,7 +257,7 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) VkSemaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; err = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, &wd->FrameIndex); - if (err == VK_ERROR_OUT_OF_DATE_KHR) + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { g_SwapChainRebuild = true; return; @@ -318,7 +330,7 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) info.pSwapchains = &wd->Swapchain; info.pImageIndices = &wd->FrameIndex; VkResult err = vkQueuePresentKHR(g_Queue, &info); - if (err == VK_ERROR_OUT_OF_DATE_KHR) + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { g_SwapChainRebuild = true; return; @@ -487,7 +499,7 @@ int main(int, char**) // Start the Dear ImGui frame ImGui_ImplVulkan_NewFrame(); - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // 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!). @@ -531,7 +543,10 @@ int main(int, char**) ImGui::Render(); ImDrawData* main_draw_data = ImGui::GetDrawData(); const bool main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0f || main_draw_data->DisplaySize.y <= 0.0f); - memcpy(&wd->ClearValue.color.float32[0], &clear_color, 4 * sizeof(float)); + wd->ClearValue.color.float32[0] = clear_color.x * clear_color.w; + wd->ClearValue.color.float32[1] = clear_color.y * clear_color.w; + wd->ClearValue.color.float32[2] = clear_color.z * clear_color.w; + wd->ClearValue.color.float32[3] = clear_color.w; if (!main_is_minimized) FrameRender(wd, main_draw_data); diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx10/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_win32_directx10/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_win32_directx10/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_win32_directx10/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj b/examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj rename to examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj index 16e24a37..e71cb0a7 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true Unicode - v110 + v140 Application true Unicode - v110 + v140 Application false true Unicode - v110 + v140 Application false true Unicode - v110 + v140 @@ -162,10 +162,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters index f76be9d0..11ad8f90 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters @@ -53,7 +53,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx10/main.cpp b/examples/interactive/imgui-1.83/examples/example_win32_directx10/main.cpp similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_win32_directx10/main.cpp rename to examples/interactive/imgui-1.83/examples/example_win32_directx10/main.cpp index 49589536..33bad387 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx10/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx10/main.cpp @@ -7,8 +7,6 @@ #include "imgui_impl_dx10.h" #include #include -#define DIRECTINPUT_VERSION 0x0800 -#include #include // Data @@ -26,8 +24,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { - ImGui_ImplWin32_EnableDpiAwareness(); - // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; @@ -94,21 +90,24 @@ int main(int, char**) ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + bool done = false; + while (!done) { // Poll and handle messages (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - if (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) + MSG msg; + while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); - continue; + if (msg.message == WM_QUIT) + done = true; } + if (done) + break; // Start the Dear ImGui frame ImGui_ImplDX10_NewFrame(); @@ -154,8 +153,9 @@ int main(int, char**) // Rendering ImGui::Render(); + const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; g_pd3dDevice->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL); - g_pd3dDevice->ClearRenderTargetView(g_mainRenderTargetView, (float*)&clear_color); + g_pd3dDevice->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha); ImGui_ImplDX10_RenderDrawData(ImGui::GetDrawData()); // Update and Render additional Platform Windows diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx11/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_win32_directx11/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_win32_directx11/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_win32_directx11/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj b/examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj similarity index 95% rename from examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj rename to examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj index 4982050f..273d351c 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -27,27 +27,27 @@ Application true Unicode - v110 + v140 Application true Unicode - v110 + v140 Application false true Unicode - v110 + v140 Application false true Unicode - v110 + v140 @@ -161,10 +161,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters index 56defdde..02cf18ca 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters @@ -53,7 +53,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx11/main.cpp b/examples/interactive/imgui-1.83/examples/example_win32_directx11/main.cpp similarity index 94% rename from examples/interactive/imgui-1.81/examples/example_win32_directx11/main.cpp rename to examples/interactive/imgui-1.83/examples/example_win32_directx11/main.cpp index e6a098d6..d6378d80 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx11/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx11/main.cpp @@ -6,8 +6,6 @@ #include "imgui_impl_win32.h" #include "imgui_impl_dx11.h" #include -#define DIRECTINPUT_VERSION 0x0800 -#include #include // Data @@ -26,8 +24,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { - ImGui_ImplWin32_EnableDpiAwareness(); - // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; @@ -59,10 +55,8 @@ int main(int, char**) //io.ConfigViewportsNoDefaultParent = true; //io.ConfigDockingAlwaysTabBar = true; //io.ConfigDockingTransparentPayload = true; -#if 1 - io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleFonts; // FIXME-DPI: THIS CURRENTLY DOESN'T WORK AS EXPECTED. DON'T USE IN USER APP! - io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleViewports; // FIXME-DPI -#endif + //io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleFonts; // FIXME-DPI: Experimental. THIS CURRENTLY DOESN'T WORK AS EXPECTED. DON'T USE IN USER APP! + //io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleViewports; // FIXME-DPI: Experimental. // Setup Dear ImGui style ImGui::StyleColorsDark(); @@ -101,21 +95,24 @@ int main(int, char**) ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + bool done = false; + while (!done) { // Poll and handle messages (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - if (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) + MSG msg; + while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); - continue; + if (msg.message == WM_QUIT) + done = true; } + if (done) + break; // Start the Dear ImGui frame ImGui_ImplDX11_NewFrame(); @@ -161,8 +158,9 @@ int main(int, char**) // Rendering ImGui::Render(); + const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL); - g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, (float*)&clear_color); + g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); // Update and Render additional Platform Windows diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx12/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_win32_directx12/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_win32_directx12/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_win32_directx12/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx12/example_win32_directx12.vcxproj b/examples/interactive/imgui-1.83/examples/example_win32_directx12/example_win32_directx12.vcxproj similarity index 99% rename from examples/interactive/imgui-1.81/examples/example_win32_directx12/example_win32_directx12.vcxproj rename to examples/interactive/imgui-1.83/examples/example_win32_directx12/example_win32_directx12.vcxproj index ab8ee013..3fed30d9 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx12/example_win32_directx12.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx12/example_win32_directx12.vcxproj @@ -21,7 +21,7 @@ {b4cf9797-519d-4afe-a8f4-5141a6b521d3} example_win32_directx12 - 10.0.14393.0 + 10.0.18362.0 @@ -169,4 +169,4 @@ - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx12/main.cpp b/examples/interactive/imgui-1.83/examples/example_win32_directx12/main.cpp similarity index 92% rename from examples/interactive/imgui-1.81/examples/example_win32_directx12/main.cpp rename to examples/interactive/imgui-1.83/examples/example_win32_directx12/main.cpp index 5cec3616..acb4eb13 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx12/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx12/main.cpp @@ -54,7 +54,6 @@ void CreateRenderTarget(); void CleanupRenderTarget(); void WaitForLastSubmittedFrame(); FrameContext* WaitForNextFrameResources(); -void ResizeSwapChain(HWND hWnd, int width, int height); LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code @@ -129,21 +128,24 @@ int main(int, char**) ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + bool done = false; + while (!done) { // Poll and handle messages (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - if (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) + MSG msg; + while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); - continue; + if (msg.message == WM_QUIT) + done = true; } + if (done) + break; // Start the Dear ImGui frame ImGui_ImplDX12_NewFrame(); @@ -205,7 +207,8 @@ int main(int, char**) g_pd3dCommandList->ResourceBarrier(1, &barrier); // Render Dear ImGui graphics - g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], (float*)&clear_color, 0, NULL); + const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; + g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], clear_color_with_alpha, 0, NULL); g_pd3dCommandList->OMSetRenderTargets(1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, NULL); g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap); ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList); @@ -348,9 +351,11 @@ bool CreateDeviceD3D(HWND hWnd) { IDXGIFactory4* dxgiFactory = NULL; IDXGISwapChain1* swapChain1 = NULL; - if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK || - dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, NULL, NULL, &swapChain1) != S_OK || - swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) + if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) + return false; + if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, NULL, NULL, &swapChain1) != S_OK) + return false; + if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) return false; swapChain1->Release(); dxgiFactory->Release(); @@ -365,7 +370,7 @@ bool CreateDeviceD3D(HWND hWnd) void CleanupDeviceD3D() { CleanupRenderTarget(); - if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = NULL; } + if (g_pSwapChain) { g_pSwapChain->SetFullscreenState(false, NULL); g_pSwapChain->Release(); g_pSwapChain = NULL; } if (g_hSwapChainWaitableObject != NULL) { CloseHandle(g_hSwapChainWaitableObject); } for (UINT i = 0; i < NUM_FRAMES_IN_FLIGHT; i++) if (g_frameContext[i].CommandAllocator) { g_frameContext[i].CommandAllocator->Release(); g_frameContext[i].CommandAllocator = NULL; } @@ -445,31 +450,6 @@ FrameContext* WaitForNextFrameResources() return frameCtx; } -void ResizeSwapChain(HWND hWnd, int width, int height) -{ - DXGI_SWAP_CHAIN_DESC1 sd; - g_pSwapChain->GetDesc1(&sd); - sd.Width = width; - sd.Height = height; - - IDXGIFactory4* dxgiFactory = NULL; - g_pSwapChain->GetParent(IID_PPV_ARGS(&dxgiFactory)); - - g_pSwapChain->Release(); - CloseHandle(g_hSwapChainWaitableObject); - - IDXGISwapChain1* swapChain1 = NULL; - dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, NULL, NULL, &swapChain1); - swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)); - swapChain1->Release(); - dxgiFactory->Release(); - - g_pSwapChain->SetMaximumFrameLatency(NUM_BACK_BUFFERS); - - g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); - assert(g_hSwapChainWaitableObject != NULL); -} - // Forward declare message handler from imgui_impl_win32.cpp extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); @@ -485,11 +465,10 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED) { WaitForLastSubmittedFrame(); - ImGui_ImplDX12_InvalidateDeviceObjects(); CleanupRenderTarget(); - ResizeSwapChain(hWnd, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); + HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); + assert(SUCCEEDED(result) && "Failed to resize swapchain."); CreateRenderTarget(); - ImGui_ImplDX12_CreateDeviceObjects(); } return 0; case WM_SYSCOMMAND: diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx9/build_win32.bat b/examples/interactive/imgui-1.83/examples/example_win32_directx9/build_win32.bat similarity index 100% rename from examples/interactive/imgui-1.81/examples/example_win32_directx9/build_win32.bat rename to examples/interactive/imgui-1.83/examples/example_win32_directx9/build_win32.bat diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj b/examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj similarity index 96% rename from examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj rename to examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj index 747dcebe..e01eca14 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,27 +28,27 @@ Application true Unicode - v110 + v140 Application true Unicode - v110 + v140 Application false true Unicode - v110 + v140 Application false true Unicode - v110 + v140 @@ -162,10 +162,10 @@ - + - + \ No newline at end of file diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters b/examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters similarity index 97% rename from examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters rename to examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters index 5197644e..9493970b 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters @@ -54,7 +54,7 @@ - + sources diff --git a/examples/interactive/imgui-1.81/examples/example_win32_directx9/main.cpp b/examples/interactive/imgui-1.83/examples/example_win32_directx9/main.cpp similarity index 95% rename from examples/interactive/imgui-1.81/examples/example_win32_directx9/main.cpp rename to examples/interactive/imgui-1.83/examples/example_win32_directx9/main.cpp index c7da3211..462883c1 100644 --- a/examples/interactive/imgui-1.81/examples/example_win32_directx9/main.cpp +++ b/examples/interactive/imgui-1.83/examples/example_win32_directx9/main.cpp @@ -6,8 +6,6 @@ #include "imgui_impl_dx9.h" #include "imgui_impl_win32.h" #include -#define DIRECTINPUT_VERSION 0x0800 -#include #include // Data @@ -24,8 +22,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code int main(int, char**) { - ImGui_ImplWin32_EnableDpiAwareness(); - // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; @@ -92,21 +88,24 @@ int main(int, char**) ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + bool done = false; + while (!done) { // Poll and handle messages (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - if (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) + MSG msg; + while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); - continue; + if (msg.message == WM_QUIT) + done = true; } + if (done) + break; // Start the Dear ImGui frame ImGui_ImplDX9_NewFrame(); @@ -155,7 +154,7 @@ int main(int, char**) g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); - D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f)); + D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*clear_color.w*255.0f), (int)(clear_color.y*clear_color.w*255.0f), (int)(clear_color.z*clear_color.w*255.0f), (int)(clear_color.w*255.0f)); g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clear_col_dx, 1.0f, 0); if (g_pd3dDevice->BeginScene() >= 0) { @@ -200,7 +199,7 @@ bool CreateDeviceD3D(HWND hWnd) ZeroMemory(&g_d3dpp, sizeof(g_d3dpp)); g_d3dpp.Windowed = TRUE; g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; - g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; + g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; // Need to use an explicit format with alpha if needing per-pixel alpha composition. g_d3dpp.EnableAutoDepthStencil = TRUE; g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16; g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // Present with vsync diff --git a/examples/interactive/imgui-1.81/examples/imgui_examples.sln b/examples/interactive/imgui-1.83/examples/imgui_examples.sln similarity index 100% rename from examples/interactive/imgui-1.81/examples/imgui_examples.sln rename to examples/interactive/imgui-1.83/examples/imgui_examples.sln diff --git a/examples/interactive/imgui-1.81/examples/libs/gl3w/GL/gl3w.c b/examples/interactive/imgui-1.83/examples/libs/gl3w/GL/gl3w.c similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/gl3w/GL/gl3w.c rename to examples/interactive/imgui-1.83/examples/libs/gl3w/GL/gl3w.c diff --git a/examples/interactive/imgui-1.81/examples/libs/gl3w/GL/gl3w.h b/examples/interactive/imgui-1.83/examples/libs/gl3w/GL/gl3w.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/gl3w/GL/gl3w.h rename to examples/interactive/imgui-1.83/examples/libs/gl3w/GL/gl3w.h diff --git a/examples/interactive/imgui-1.81/examples/libs/gl3w/GL/glcorearb.h b/examples/interactive/imgui-1.83/examples/libs/gl3w/GL/glcorearb.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/gl3w/GL/glcorearb.h rename to examples/interactive/imgui-1.83/examples/libs/gl3w/GL/glcorearb.h diff --git a/examples/interactive/imgui-1.81/examples/libs/glfw/COPYING.txt b/examples/interactive/imgui-1.83/examples/libs/glfw/COPYING.txt similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/glfw/COPYING.txt rename to examples/interactive/imgui-1.83/examples/libs/glfw/COPYING.txt diff --git a/examples/interactive/imgui-1.81/examples/libs/glfw/include/GLFW/glfw3.h b/examples/interactive/imgui-1.83/examples/libs/glfw/include/GLFW/glfw3.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/glfw/include/GLFW/glfw3.h rename to examples/interactive/imgui-1.83/examples/libs/glfw/include/GLFW/glfw3.h diff --git a/examples/interactive/imgui-1.81/examples/libs/glfw/include/GLFW/glfw3native.h b/examples/interactive/imgui-1.83/examples/libs/glfw/include/GLFW/glfw3native.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/glfw/include/GLFW/glfw3native.h rename to examples/interactive/imgui-1.83/examples/libs/glfw/include/GLFW/glfw3native.h diff --git a/examples/interactive/imgui-1.81/examples/libs/glfw/lib-vc2010-32/glfw3.lib b/examples/interactive/imgui-1.83/examples/libs/glfw/lib-vc2010-32/glfw3.lib similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/glfw/lib-vc2010-32/glfw3.lib rename to examples/interactive/imgui-1.83/examples/libs/glfw/lib-vc2010-32/glfw3.lib diff --git a/examples/interactive/imgui-1.81/examples/libs/glfw/lib-vc2010-64/glfw3.lib b/examples/interactive/imgui-1.83/examples/libs/glfw/lib-vc2010-64/glfw3.lib similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/glfw/lib-vc2010-64/glfw3.lib rename to examples/interactive/imgui-1.83/examples/libs/glfw/lib-vc2010-64/glfw3.lib diff --git a/examples/interactive/imgui-1.81/examples/libs/usynergy/README.txt b/examples/interactive/imgui-1.83/examples/libs/usynergy/README.txt similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/usynergy/README.txt rename to examples/interactive/imgui-1.83/examples/libs/usynergy/README.txt diff --git a/examples/interactive/imgui-1.81/examples/libs/usynergy/uSynergy.c b/examples/interactive/imgui-1.83/examples/libs/usynergy/uSynergy.c similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/usynergy/uSynergy.c rename to examples/interactive/imgui-1.83/examples/libs/usynergy/uSynergy.c diff --git a/examples/interactive/imgui-1.81/examples/libs/usynergy/uSynergy.h b/examples/interactive/imgui-1.83/examples/libs/usynergy/uSynergy.h similarity index 100% rename from examples/interactive/imgui-1.81/examples/libs/usynergy/uSynergy.h rename to examples/interactive/imgui-1.83/examples/libs/usynergy/uSynergy.h diff --git a/examples/interactive/imgui-1.81/imconfig.h b/examples/interactive/imgui-1.83/imconfig.h similarity index 85% rename from examples/interactive/imgui-1.81/imconfig.h rename to examples/interactive/imgui-1.83/imconfig.h index c5e1650a..a0c86caa 100644 --- a/examples/interactive/imgui-1.81/imconfig.h +++ b/examples/interactive/imgui-1.83/imconfig.h @@ -20,7 +20,9 @@ //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows -// Using dear imgui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. //#define IMGUI_API __declspec( dllexport ) //#define IMGUI_API __declspec( dllimport ) @@ -34,14 +36,17 @@ //#define IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger window: ShowMetricsWindow() will be empty. //---- Don't implement some functions to reduce linkage requirements. -//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. -//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) +//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. -//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. +//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Include imgui_user.h at the end of imgui.h as a convenience //#define IMGUI_INCLUDE_IMGUI_USER_H @@ -65,7 +70,7 @@ //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). -// On Windows you may use vcpkg with 'vcpkg install freetype' + 'vcpkg integrate install'. +// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. //#define IMGUI_ENABLE_FREETYPE //---- Use stb_truetype to build and rasterize the font atlas (default) diff --git a/examples/interactive/imgui-1.81/imgui.cpp b/examples/interactive/imgui-1.83/imgui.cpp similarity index 90% rename from examples/interactive/imgui-1.81/imgui.cpp rename to examples/interactive/imgui-1.83/imgui.cpp index 1d5c88a1..105d45ed 100644 --- a/examples/interactive/imgui-1.81/imgui.cpp +++ b/examples/interactive/imgui-1.83/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (main code and documentation) // Help: @@ -11,15 +11,16 @@ // - FAQ http://dearimgui.org/faq // - Homepage & latest https://github.com/ocornut/imgui // - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/3488 (please post your screenshots/video there!) +// - Gallery https://github.com/ocornut/imgui/issues/3793 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Wiki https://github.com/ocornut/imgui/wiki // - Issues & support https://github.com/ocornut/imgui/issues +// - Discussions https://github.com/ocornut/imgui/discussions // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. -// Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring contracts. Please reach out to "contact AT dearimgui.org". +// Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring contracts. Please reach out to "contact AT dearimgui.com". // Individuals: you can support continued development via donations. See docs/README or web page. // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. @@ -134,17 +135,17 @@ CODE READ FIRST ---------- - - Remember to read the FAQ (https://www.dearimgui.org/faq) - - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction - or destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, less bugs. + - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki) + - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction or + destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, fewer bugs. - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build. - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori). - You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in the FAQ. + You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki. - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances. - For every application frame your UI code will be called only once. This is in contrast to e.g. Unity's own implementation of an IMGUI, + For every application frame, your UI code will be called only once. This is in contrast to e.g. Unity's implementation of an IMGUI, where the UI code is called multiple times ("multiple passes") from a single entry point. There are pros and cons to both approaches. - - Our origin are on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right. + - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right. - This codebase is also optimized to yield decent performances with typical "Debug" builds settings. - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected). If you get an assert, read the messages and comments around the assert. @@ -157,8 +158,8 @@ CODE HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI ---------------------------------------------- - - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) - - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over master. + - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h) + - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over "master". - You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file. - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed @@ -173,12 +174,12 @@ CODE - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library. - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder. - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build system. - It is recommended you build and statically link the .cpp files as part of your project and NOT as shared library (DLL). + It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared library (DLL). - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types with your own maths types. - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide. Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" - phases of your own application. All rendering information are stored into command-lists that you will retrieve after calling ImGui::Render(). + phases of your own application. All rendering information is stored into command-lists that you will retrieve after calling ImGui::Render(). - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code. - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_*** files from the examples/ folder. @@ -186,7 +187,7 @@ CODE HOW A SIMPLE APPLICATION MAY LOOK LIKE -------------------------------------- EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). - The sub-folders in examples/ contains examples applications following this structure. + The sub-folders in examples/ contain examples applications following this structure. // Application init: create a dear imgui context, setup some options, load fonts ImGui::CreateContext(); @@ -236,7 +237,7 @@ CODE unsigned char* pixels = NULL; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - // At this point you've got the texture data and you need to upload that your your graphic system: + // At this point you've got the texture data and you need to upload that to your graphic system: // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) @@ -255,7 +256,7 @@ CODE io.MouseDown[1] = my_mouse_buttons[1]; // Call NewFrame(), after this point you can use ImGui::* functions anytime - // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use Dear ImGui everywhere) + // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere) ImGui::NewFrame(); // Most of your application code here @@ -275,14 +276,14 @@ CODE // Shutdown ImGui::DestroyContext(); - To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest your application, + To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! Please read the FAQ and example applications for details about this! HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE --------------------------------------------- - The backends in impl_impl_XXX.cpp files contains many working implementations of a rendering function. + The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. void void MyImGuiRenderFunction(ImDrawData* draw_data) { @@ -304,11 +305,11 @@ CODE } else { - // The texture for the draw call is specified by pcmd->TextureId. + // The texture for the draw call is specified by pcmd->GetTexID(). // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization. - MyEngineBindTexture((MyTexture*)pcmd->TextureId); + MyEngineBindTexture((MyTexture*)pcmd->GetTexID()); - // We are using scissoring to clip some objects. All low-level graphics API should supports it. + // We are using scissoring to clip some objects. All low-level graphics API should support it. // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches // (some elements visible outside their bounds) but you can fix that once everything else works! // - Clipping coordinates are provided in imgui coordinates space: @@ -351,7 +352,7 @@ CODE Note that io.NavInputs[] is cleared by EndFrame(). - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. - - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + - We use a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://dearimgui.org/controls_sheets - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo @@ -363,7 +364,7 @@ CODE Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements. When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that. - (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!) + (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse moving back and forth!) (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want to set a boolean to ignore your other external mouse positions until the external source is moved again.) @@ -373,18 +374,43 @@ CODE Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. - When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. + 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. - (Docking/Viewport Branch) - - 2020/XX/XX (1.XX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: +(Docking/Viewport Branch) + - 2021/XX/XX (1.XX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore. you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) - likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. - - 2020/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. - - + - 2021/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. + + + - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019): + - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList() + - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder + - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID(). + - if you are using official backends from the source tree: you have nothing to do. + - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID(). + - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags. + - ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft + - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight + - ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc. + flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API. + breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no corners": + - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use) + - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use) + - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use) + - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f. + this ONLY matters for hard coded use of 0 + rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok. + the old ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" and we sometimes encouraged using them as shortcuts. + legacy path still support use of hard coded ~0 or any value from 0x1 or 0xF. They will behave the same with legacy paths enabled (will assert otherwise). + - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018): + - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY() + - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius < 0.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect on anti-aliasing. + - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags flags". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future. + - 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. + - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed. - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete). - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete). - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete). @@ -414,7 +440,7 @@ CODE - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete sooner because previous name was added recently). - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton. - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more self-explanatory. - - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on item - because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. + - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item - because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this value after calling AddFontDefault(), this is now done automatically. It was also getting in the way of better font scaling, so let's get rid of it now! - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). @@ -423,7 +449,7 @@ CODE - if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may warn on float>int conversion, 2/ everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning, and be technically correct. - if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may warn on float>int conversion, 2/ code will assert at runtime, 3/ in case asserts are disabled, the code will not crash and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with ImGuiSliderFlags_Logarithmic to fix the warning/assert and get a _similar_ effect as previous uses of power >1.0f. see https://github.com/ocornut/imgui/issues/3361 for all details. - kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version were removed directly as they were most unlikely ever used. + kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version was removed directly as they were most unlikely ever used. for shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the ImGuiSliderFlags_ReadOnly internal flag in the meantime. - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems. @@ -443,7 +469,7 @@ CODE - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS - - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was the vaguely documented and rarely if ever used). Instead we added an explicit PrimUnreserve() API. + - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented and rarely if ever used). Instead, we added an explicit PrimUnreserve() API. - 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other mouse functions (none of the other functions have it). - 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000) to conform with supporting Unicode planes 1-16 in a future update. ID below 0x110000 will now assert. - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency. @@ -471,7 +497,7 @@ CODE - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete). - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete). - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete). - - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If for some reason your time step calculation gives you a zero value, replace it with an arbitrary small value! + - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If for some reason your time step calculation gives you a zero value, replace it with an arbitrarily small value! - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already). - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead! - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete). @@ -566,12 +592,12 @@ CODE - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. - - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetID() and use it instead of passing string to BeginChild(). - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. - - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully, breakage should be minimal. - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. - If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you, otherwise if <1.0f you need tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you, otherwise if <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color: ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); } If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. @@ -579,7 +605,7 @@ CODE - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. - - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref GitHub issue #337). - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. @@ -610,7 +636,7 @@ CODE - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence - - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely used. Sorry! - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. @@ -665,7 +691,7 @@ CODE =========== Q: Where is the documentation? - A: This library is poorly documented at the moment and expects of the user to be acquainted with C/C++. + A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++. - Run the examples/ and explore them. - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function. - The demo covers most features of Dear ImGui, so you can read the code and see its output. @@ -675,7 +701,7 @@ CODE - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links. - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful. - Your programming IDE is your friend, find the type or function declaration to find comments - associated to it. + associated with it. Q: What is this library called? Q: Which version should I get? @@ -688,15 +714,15 @@ CODE Q: How to get started? A: Read 'PROGRAMMER GUIDE' above. Read examples/README.txt. - Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or to my application? + Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application? A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! - >> See https://www.dearimgui.org/faq for fully detailed answer. You really want to read this. + >> See https://www.dearimgui.org/faq for a fully detailed answer. You really want to read this. Q. How can I enable keyboard controls? Q: How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display) - Q: I integrated Dear ImGui in my engine and little squares are showing instead of text.. - Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. - Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries.. + Q: I integrated Dear ImGui in my engine and little squares are showing instead of text... + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around... + Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries... >> See https://www.dearimgui.org/faq Q&A: Usage @@ -734,16 +760,16 @@ CODE ============== Q: How can I help? - A: - Businesses: please reach out to "contact AT dearimgui.org" if you work in a place using Dear ImGui! + A: - Businesses: please reach out to "contact AT dearimgui.com" if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. - This is among the most useful thing you can do for Dear ImGui. With increased funding we can hire more people working on this project. + This is among the most useful thing you can do for Dear ImGui. With increased funding, we can hire more people working on this project. - Individuals: you can support continued development via PayPal donations. See README. - - If you are experienced with Dear ImGui and C++, look at the github issues, look at the Wiki, read docs/TODO.txt + - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, read docs/TODO.txt and see how you want to help and can help! - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. - You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/3488). Visuals are ideal as they inspire other programmers. - But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. - - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). + You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on GitHub or privately). */ @@ -772,6 +798,11 @@ CODE #include // intptr_t #endif +// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled +#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) +#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS +#endif + // [Windows] OS specific includes (optional) #if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) #define IMGUI_DISABLE_WIN32_FUNCTIONS @@ -806,6 +837,9 @@ CODE #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif // Clang/GCC warnings with -Weverything @@ -848,7 +882,7 @@ static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) -static const float WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS = 4.0f; // Extend outside and inside windows. Affect FindHoveredWindow(). +static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. @@ -890,12 +924,13 @@ static void NavUpdateInitResult(); static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); static void NavEndFrame(); -static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand); -static void NavApplyItemToResult(ImGuiNavMoveResult* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel); +static bool NavScoreItem(ImGuiNavItemData* result, ImRect cand); +static void NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel); static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id); static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); +static void NavRestoreLayer(ImGuiNavLayer layer); static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking @@ -919,10 +954,10 @@ const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbi static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags); static void UpdateViewportsNewFrame(); static void UpdateViewportsEndFrame(); -static void UpdateSelectWindowViewport(ImGuiWindow* window); +static void WindowSelectViewport(ImGuiWindow* window); +static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack); static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport); static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window); -static void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport); static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window); static int FindPlatformMonitorForPos(const ImVec2& pos); static int FindPlatformMonitorForRect(const ImRect& r); @@ -934,27 +969,33 @@ static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport); // [SECTION] CONTEXT AND MEMORY ALLOCATORS //----------------------------------------------------------------------------- +// DLL users: +// - Heaps and globals are not shared across DLL boundaries! +// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling from. +// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading mechanisms work without DLL). +// - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current location (which DLL boundary you are in). + // Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL. -// ImGui::CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). -// 1) Important: globals are not shared across DLL boundaries! If you use DLLs or any form of hot-reloading: you will need to call -// SetCurrentContext() (with the pointer you got from CreateContext) from each unique static/DLL boundary, and after each hot-reloading. -// In your debugger, add GImGui to your watch window and notice how its value changes depending on which location you are currently stepping into. -// 2) Important: Dear ImGui functions are not thread-safe because of this pointer. -// If you want thread-safety to allow N threads to access N different contexts, you can: -// - Change this variable to use thread local storage so each thread can refer to a different context, in imconfig.h: -// struct ImGuiContext; -// extern thread_local ImGuiContext* MyImGuiTLS; -// #define GImGui MyImGuiTLS -// And then define MyImGuiTLS in one of your cpp file. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword. -// - Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 -// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from different namespace. +// - ImGui::CreateContext() will automatically set this pointer if it is NULL. +// Change to a different context by calling ImGui::SetCurrentContext(). +// - Important: Dear ImGui functions are not thread-safe because of this pointer. +// If you want thread-safety to allow N threads to access N different contexts: +// - Change this variable to use thread local storage so each thread can refer to a different context, in your imconfig.h: +// struct ImGuiContext; +// extern thread_local ImGuiContext* MyImGuiTLS; +// #define GImGui MyImGuiTLS +// And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword. +// - Future development aims to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a different namespace. +// - DLL users: read comments above. #ifndef GImGui ImGuiContext* GImGui = NULL; #endif // Memory Allocator functions. Use SetAllocatorFunctions() to change them. -// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. -// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +// - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +// - DLL users: read comments above. #ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); return malloc(size); } static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); free(ptr); } @@ -962,10 +1003,9 @@ static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_d static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(size); IM_ASSERT(0); return NULL; } static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(ptr); IM_ASSERT(0); } #endif - -static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; -static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; -static void* GImAllocatorUserData = NULL; +static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper; +static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper; +static void* GImAllocatorUserData = NULL; //----------------------------------------------------------------------------- // [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) @@ -1011,7 +1051,7 @@ ImGuiStyle::ImGuiStyle() AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - CircleSegmentMaxError = 1.60f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Default theme ImGui::StyleColorsDark(this); @@ -1058,7 +1098,7 @@ ImGuiIO::ImGuiIO() DisplaySize = ImVec2(-1.0f, -1.0f); DeltaTime = 1.0f / 60.0f; IniSavingRate = 5.0f; - IniFilename = "imgui.ini"; + IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; @@ -1076,7 +1116,6 @@ ImGuiIO::ImGuiIO() // Docking options (when ImGuiConfigFlags_DockingEnable is set) ConfigDockingNoSplit = false; - ConfigDockingWithShift = false; ConfigDockingAlwaysTabBar = false; ConfigDockingTransparentPayload = false; @@ -1142,11 +1181,18 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) if (InputQueueSurrogate != 0) { if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate + { InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID); - else if (IM_UNICODE_CODEPOINT_MAX == (0xFFFF)) // Codepoint will not fit in ImWchar (extra parenthesis around 0xFFFF somehow fixes -Wunreachable-code with Clang) - cp = IM_UNICODE_CODEPOINT_INVALID; + } else + { +#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF + cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar +#else cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000); +#endif + } + InputQueueSurrogate = 0; } InputQueueCharacters.push_back(cp); @@ -1697,7 +1743,7 @@ int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) } // Based on stb_to_utf8() from github.com/nothings/stb/ -static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c) { if (c < 0x80) { @@ -1732,6 +1778,13 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) return 0; } +const char* ImTextCharToUtf8(char out_buf[5], unsigned int c) +{ + int count = ImTextCharToUtf8_inline(out_buf, 5, c); + out_buf[count] = 0; + return out_buf; +} + // Not optimal but we very rarely use this function. int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) { @@ -1748,20 +1801,20 @@ static inline int ImTextCountUtf8BytesFromChar(unsigned int c) return 3; } -int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end) { - char* buf_out = buf; - const char* buf_end = buf + buf_size; - while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + char* buf_p = out_buf; + const char* buf_end = out_buf + out_buf_size; + while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) { unsigned int c = (unsigned int)(*in_text++); if (c < 0x80) - *buf_out++ = (char)c; + *buf_p++ = (char)c; else - buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c); + buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c); } - *buf_out = 0; - return (int)(buf_out - buf); + *buf_p = 0; + return (int)(buf_p - out_buf); } int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) @@ -2782,7 +2835,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con int ellipsis_char_count = 1; if (ellipsis_char == (ImWchar)-1) { - ellipsis_char = (ImWchar)'.'; + ellipsis_char = font->DotChar; ellipsis_char_count = 3; } const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char); @@ -2842,8 +2895,8 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, const float border_size = g.Style.FrameBorderSize; if (border && border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); } } @@ -2854,8 +2907,8 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) const float border_size = g.Style.FrameBorderSize; if (border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); } } @@ -2881,13 +2934,13 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, 0, THICKNESS); if (!fully_visible) window->DrawList->PopClipRect(); } if (flags & ImGuiNavHighlightFlags_TypeThin) { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, 1.0f); } } @@ -2927,8 +2980,7 @@ ImGuiWindow::~ImGuiWindow() { IM_ASSERT(DrawList == &DrawListInst); IM_DELETE(Name); - for (int i = 0; i != ColumnsStorage.Size; i++) - ColumnsStorage[i].~ImGuiOldColumns(); + ColumnsStorage.clear_destruct(); } ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) @@ -3062,6 +3114,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdTimer = 0.0f; g.ActiveIdHasBeenPressedBefore = false; g.ActiveIdHasBeenEditedBefore = false; + g.ActiveIdMouseButton = -1; if (id != 0) { g.LastActiveId = id; @@ -3127,7 +3180,7 @@ void ImGui::MarkItemEdited(ImGuiID id) //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.ActiveIdHasBeenEditedThisFrame = true; g.ActiveIdHasBeenEditedBefore = true; - g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; } static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) @@ -3136,8 +3189,8 @@ static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFla // FIXME-OPT: This could be cached/stored within the window. ImGuiContext& g = *GImGui; if (g.NavWindow) - if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) - if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindowDockTree) + if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree) { // For the purpose of those flags we differentiate "standard popup" from "modal popup" // NB: The order of those two tests is important because Modal windows are also Popups. @@ -3149,7 +3202,7 @@ static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFla // Filter by viewport if (window->Viewport != g.MouseViewport) - if (g.MovingWindow == NULL || window->RootWindow != g.MovingWindow->RootWindow) + if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree) return false; return true; @@ -3163,24 +3216,30 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (g.NavDisableMouseHover && !g.NavDisableHighlight) + { + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; return IsItemFocused(); + } // Test for bounding box overlap, as updated as ItemAdd() - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags; + if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) return false; IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function // Test if we are hovering the right window (our window could be behind another window) - // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. - // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. - //if (g.HoveredWindow != window) - // return false; - if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) - return false; + // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable + // to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was + // the test that has been running for a long while. + if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0) + if ((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0) + return false; // Test if another item is active (e.g. being dragged) - if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) + if (g.ActiveId != 0 && g.ActiveId != g.LastItemData.ID && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) return false; // Test if interactions on this window are blocked by an active popup or modal. @@ -3189,12 +3248,12 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; // Test if the item is disabled - if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; // Special handling for calling after Begin() which represent the title bar or tab. // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. - if ((window->DC.LastItemId == window->ID || window->DC.LastItemId == window->MoveId) && window->WriteAccessed) + if ((g.LastItemData.ID == window->ID || g.LastItemData.ID == window->MoveId) && window->WriteAccessed) return false; return true; } @@ -3215,7 +3274,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; if (g.NavDisableMouseHover) return false; - if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None) || (window->DC.ItemFlags & ImGuiItemFlags_Disabled)) + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdDisabled = true; return false; @@ -3224,9 +3283,21 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level // hover test in widgets code. We could also decide to split this function is two. if (id != 0) - { SetHoveredID(id); + // When disabled we'll return false but still set HoveredId + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); + if (item_flags & ImGuiItemFlags_Disabled) + { + // Release active id if turning disabled + if (g.ActiveId == id) + ClearActiveID(); + g.HoveredIdDisabled = true; + return false; + } + + if (id != 0) + { // [DEBUG] Item Picker tool! // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered @@ -3254,55 +3325,60 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged // This is also inlined in ItemAdd() // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect! -void ImGui::SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) +void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) { - window->DC.LastItemId = item_id; - window->DC.LastItemStatusFlags = item_flags; - window->DC.LastItemRect = item_rect; + ImGuiContext& g = *GImGui; + g.LastItemData.ID = item_id; + g.LastItemData.InFlags = in_flags; + g.LastItemData.StatusFlags = item_flags; + g.LastItemData.Rect = item_rect; } +// Called by ItemAdd() // Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out. -bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id) +void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) { ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0 && id == g.LastItemData.ID); // Increment counters - const bool is_tab_stop = (window->DC.ItemFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; + // FIXME: ImGuiItemFlags_Disabled should disable more. + const bool is_tab_stop = (g.LastItemData.InFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; window->DC.FocusCounterRegular++; if (is_tab_stop) + { window->DC.FocusCounterTabStop++; + if (g.NavId == id) + g.NavIdTabCounter = window->DC.FocusCounterTabStop; + } // Process TAB/Shift-TAB to tab *OUT* of the currently focused item. // (Note that we can always TAB out of a widget that doesn't allow tabbing in) - if (g.ActiveId == id && g.FocusTabPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) && g.FocusRequestNextWindow == NULL) + if (g.ActiveId == id && g.TabFocusPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) && g.TabFocusRequestNextWindow == NULL) { - g.FocusRequestNextWindow = window; - g.FocusRequestNextCounterTabStop = window->DC.FocusCounterTabStop + (g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + g.TabFocusRequestNextWindow = window; + g.TabFocusRequestNextCounterTabStop = window->DC.FocusCounterTabStop + (g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. } // Handle focus requests - if (g.FocusRequestCurrWindow == window) + if (g.TabFocusRequestCurrWindow == window) { - if (window->DC.FocusCounterRegular == g.FocusRequestCurrCounterRegular) - return true; - if (is_tab_stop && window->DC.FocusCounterTabStop == g.FocusRequestCurrCounterTabStop) + if (window->DC.FocusCounterRegular == g.TabFocusRequestCurrCounterRegular) + { + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByCode; + return; + } + if (is_tab_stop && window->DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop) { g.NavJustTabbedId = id; - return true; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing; + return; } // If another item is about to be focused, we clear our own active id if (g.ActiveId == id) ClearActiveID(); } - - return false; -} - -void ImGui::FocusableItemUnregister(ImGuiWindow* window) -{ - window->DC.FocusCounterRegular--; - window->DC.FocusCounterTabStop--; } float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) @@ -3334,7 +3410,7 @@ void* ImGui::MemAlloc(size_t size) { if (ImGuiContext* ctx = GImGui) ctx->IO.MetricsActiveAllocations++; - return GImAllocatorAllocFunc(size, GImAllocatorUserData); + return (*GImAllocatorAllocFunc)(size, GImAllocatorUserData); } // IM_FREE() == ImGui::MemFree() @@ -3343,7 +3419,7 @@ void ImGui::MemFree(void* ptr) if (ptr) if (ImGuiContext* ctx = GImGui) ctx->IO.MetricsActiveAllocations--; - return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); + return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData); } const char* ImGui::GetClipboardText() @@ -3380,13 +3456,21 @@ void ImGui::SetCurrentContext(ImGuiContext* ctx) #endif } -void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data) +void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data) { GImAllocatorAllocFunc = alloc_func; GImAllocatorFreeFunc = free_func; GImAllocatorUserData = user_data; } +// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space) +void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data) +{ + *p_alloc_func = GImAllocatorAllocFunc; + *p_free_func = GImAllocatorFreeFunc; + *p_user_data = GImAllocatorUserData; +} + ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) { ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); @@ -3526,11 +3610,12 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) FocusWindow(window); SetActiveID(window->MoveId, window); g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos; g.ActiveIdNoClearOnFocusLoss = true; - g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos; + SetActiveIdUsingNavAndKeys(); bool can_move_window = true; - if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) + if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove)) can_move_window = false; if (ImGuiDockNode* node = window->DockNodeAsHost) if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove)) @@ -3578,8 +3663,8 @@ void ImGui::UpdateMouseMovingWindowNewFrame() // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. KeepAliveID(g.ActiveId); - IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); - ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree); + ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree; if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) { ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; @@ -3609,8 +3694,8 @@ void ImGui::UpdateMouseMovingWindowNewFrame() // Clear the NoInput window flag set by the Viewport system moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; // FIXME-VIEWPORT: Test engine managed to crash here because Viewport was NULL. - ClearActiveID(); g.MovingWindow = NULL; + ClearActiveID(); } } else @@ -3643,7 +3728,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() { // Handle the edge case of a popup being closed while clicking in its empty space. // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. - ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindowDockStop : NULL; + ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel); if (root_window != NULL && !is_closed_popup) @@ -3689,8 +3774,6 @@ static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) window->DC.CursorPos += delta; window->DC.CursorStartPos += delta; window->DC.CursorMaxPos += delta; - window->DC.LastItemRect.Translate(delta); - window->DC.LastItemDisplayRect.Translate(delta); } static void ScaleWindow(ImGuiWindow* window, float scale) @@ -3809,7 +3892,7 @@ void ImGui::UpdateMouseWheel() const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); const float scale = new_font_scale / window->FontWindowScale; window->FontWindowScale = new_font_scale; - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) + if (window == window->RootWindow) { const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; SetWindowPos(window, window->Pos + offset, 0); @@ -3821,10 +3904,17 @@ void ImGui::UpdateMouseWheel() // Mouse wheel scrolling // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent + if (g.IO.KeyCtrl) + return; + + // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead + // (we avoid doing it on OSX as it the OS input layer handles this already) + const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors; + const float wheel_y = swap_axis ? 0.0f : g.IO.MouseWheel; + const float wheel_x = swap_axis ? g.IO.MouseWheel : g.IO.MouseWheelH; // Vertical Mouse Wheel scrolling - const float wheel_y = (g.IO.MouseWheel != 0.0f && !g.IO.KeyShift) ? g.IO.MouseWheel : 0.0f; - if (wheel_y != 0.0f && !g.IO.KeyCtrl) + if (wheel_y != 0.0f) { StartLockWheelingWindow(window); while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) @@ -3838,8 +3928,7 @@ void ImGui::UpdateMouseWheel() } // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held - const float wheel_x = (g.IO.MouseWheelH != 0.0f && !g.IO.KeyShift) ? g.IO.MouseWheelH : (g.IO.MouseWheel != 0.0f && g.IO.KeyShift) ? g.IO.MouseWheel : 0.0f; - if (wheel_x != 0.0f && !g.IO.KeyCtrl) + if (wheel_x != 0.0f) { StartLockWheelingWindow(window); while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) @@ -3858,32 +3947,34 @@ void ImGui::UpdateTabFocus() ImGuiContext& g = *GImGui; // Pressing TAB activate widget focus - g.FocusTabPressed = (g.NavWindow && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)); - if (g.ActiveId == 0 && g.FocusTabPressed) - { - // Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also - // manipulate the Next fields even, even though they will be turned into Curr fields by the code below. - g.FocusRequestNextWindow = g.NavWindow; - g.FocusRequestNextCounterRegular = INT_MAX; + g.TabFocusPressed = (g.NavWindow && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)); + if (g.ActiveId == 0 && g.TabFocusPressed) + { + // - This path is only taken when no widget are active/tabbed-into yet. + // Subsequent tabbing will be processed by FocusableItemRegister() + // - Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also + // manipulate the Next fields here even though they will be turned into Curr fields below. + g.TabFocusRequestNextWindow = g.NavWindow; + g.TabFocusRequestNextCounterRegular = INT_MAX; if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) - g.FocusRequestNextCounterTabStop = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); + g.TabFocusRequestNextCounterTabStop = g.NavIdTabCounter + (g.IO.KeyShift ? -1 : 0); else - g.FocusRequestNextCounterTabStop = g.IO.KeyShift ? -1 : 0; + g.TabFocusRequestNextCounterTabStop = g.IO.KeyShift ? -1 : 0; } // Turn queued focus request into current one - g.FocusRequestCurrWindow = NULL; - g.FocusRequestCurrCounterRegular = g.FocusRequestCurrCounterTabStop = INT_MAX; - if (g.FocusRequestNextWindow != NULL) + g.TabFocusRequestCurrWindow = NULL; + g.TabFocusRequestCurrCounterRegular = g.TabFocusRequestCurrCounterTabStop = INT_MAX; + if (g.TabFocusRequestNextWindow != NULL) { - ImGuiWindow* window = g.FocusRequestNextWindow; - g.FocusRequestCurrWindow = window; - if (g.FocusRequestNextCounterRegular != INT_MAX && window->DC.FocusCounterRegular != -1) - g.FocusRequestCurrCounterRegular = ImModPositive(g.FocusRequestNextCounterRegular, window->DC.FocusCounterRegular + 1); - if (g.FocusRequestNextCounterTabStop != INT_MAX && window->DC.FocusCounterTabStop != -1) - g.FocusRequestCurrCounterTabStop = ImModPositive(g.FocusRequestNextCounterTabStop, window->DC.FocusCounterTabStop + 1); - g.FocusRequestNextWindow = NULL; - g.FocusRequestNextCounterRegular = g.FocusRequestNextCounterTabStop = INT_MAX; + ImGuiWindow* window = g.TabFocusRequestNextWindow; + g.TabFocusRequestCurrWindow = window; + if (g.TabFocusRequestNextCounterRegular != INT_MAX && window->DC.FocusCounterRegular != -1) + g.TabFocusRequestCurrCounterRegular = ImModPositive(g.TabFocusRequestNextCounterRegular, window->DC.FocusCounterRegular + 1); + if (g.TabFocusRequestNextCounterTabStop != INT_MAX && window->DC.FocusCounterTabStop != -1) + g.TabFocusRequestCurrCounterTabStop = ImModPositive(g.TabFocusRequestNextCounterTabStop, window->DC.FocusCounterTabStop + 1); + g.TabFocusRequestNextWindow = NULL; + g.TabFocusRequestNextCounterRegular = g.TabFocusRequestNextCounterTabStop = INT_MAX; } g.NavIdTabCounter = INT_MAX; @@ -3893,6 +3984,7 @@ void ImGui::UpdateTabFocus() void ImGui::UpdateHoveredWindowAndCaptureFlags() { ImGuiContext& g = *GImGui; + g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING)); // Find the window hovered by mouse: // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. @@ -3904,7 +3996,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() // Modal windows prevents mouse from hovering behind them. ImGuiWindow* modal_window = GetTopMostPopupModal(); - if (modal_window && g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + if (modal_window && g.HoveredWindow && !IsWindowChildOf(g.HoveredWindow->RootWindowDockTree, modal_window)) clear_hovered_windows = true; // Disabled mouse? @@ -3932,7 +4024,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() clear_hovered_windows = true; if (clear_hovered_windows) - g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to Dear ImGui + app) if (g.WantCaptureMouseNextFrame != -1) @@ -3967,7 +4059,7 @@ void ImGui::NewFrame() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); ImGuiContext& g = *GImGui; - + // Remove pending delete hooks before frame start. // This deferred removal avoid issues of removal while iterating the hook vector for (int n = g.Hooks.Size - 1; n >= 0; n--) @@ -3995,7 +4087,8 @@ void ImGui::NewFrame() g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); - g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX; + g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame)); + g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX; UpdateViewportsNewFrame(); @@ -4009,7 +4102,7 @@ void ImGui::NewFrame() virtual_space.Add(g.Viewports[n]->GetMainRect()); g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; - g.DrawListSharedData.SetCircleSegmentMaxError(g.Style.CircleSegmentMaxError); + g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; @@ -4120,7 +4213,7 @@ void ImGui::NewFrame() UpdateTabFocus(); // Mark all windows as not visible and compact unused memory. - IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size); + IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; for (int i = 0; i != g.Windows.Size; i++) { @@ -4139,6 +4232,9 @@ void ImGui::NewFrame() for (int i = 0; i < g.TablesLastTimeActive.Size; i++) if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) TableGcCompactTransientBuffers(g.Tables.GetByIndex(i)); + for (int i = 0; i < g.TablesTempDataStack.Size; i++) + if (g.TablesTempDataStack[i].LastTimeActive >= 0.0f && g.TablesTempDataStack[i].LastTimeActive < memory_compact_start_time) + TableGcCompactTransientBuffers(&g.TablesTempDataStack[i]); if (g.GcCompactAll) GcCompactTransientMiscBuffers(); g.GcCompactAll = false; @@ -4152,9 +4248,8 @@ void ImGui::NewFrame() g.CurrentWindowStack.resize(0); g.BeginPopupStack.resize(0); g.ItemFlagsStack.resize(0); - g.ItemFlagsStack.push_back(ImGuiItemFlags_Default_); + g.ItemFlagsStack.push_back(ImGuiItemFlags_None); g.GroupStack.resize(0); - ClosePopupsOverWindow(g.NavWindow, false); // Docking DockContextNewFrameUpdateDocking(&g); @@ -4181,20 +4276,20 @@ void ImGui::UpdateDebugToolItemPicker() if (g.DebugItemPickerActive) { const ImGuiID hovered_id = g.HoveredIdPreviousFrame; - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + SetMouseCursor(ImGuiMouseCursor_Hand); + if (IsKeyPressedMap(ImGuiKey_Escape)) g.DebugItemPickerActive = false; - if (ImGui::IsMouseClicked(0) && hovered_id) + if (IsMouseClicked(0) && hovered_id) { g.DebugItemPickerBreakId = hovered_id; g.DebugItemPickerActive = false; } - ImGui::SetNextWindowBgAlpha(0.60f); - ImGui::BeginTooltip(); - ImGui::Text("HoveredId: 0x%08X", hovered_id); - ImGui::Text("Press ESC to abort picking."); - ImGui::TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), "Click to break in debugger!"); - ImGui::EndTooltip(); + SetNextWindowBgAlpha(0.60f); + BeginTooltip(); + Text("HoveredId: 0x%08X", hovered_id); + Text("Press ESC to abort picking."); + TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), "Click to break in debugger!"); + EndTooltip(); } } @@ -4216,10 +4311,8 @@ void ImGui::Initialize(ImGuiContext* context) g.SettingsHandlers.push_back(ini_handler); } -#ifdef IMGUI_HAS_TABLE // Add .ini handle for ImGuiTable type TableSettingsInstallHandler(context); -#endif // #ifdef IMGUI_HAS_TABLE // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); @@ -4275,16 +4368,14 @@ void ImGui::Shutdown(ImGuiContext* context) CallContextHooks(&g, ImGuiContextHookType_Shutdown); // Clear everything else - for (int i = 0; i < g.Windows.Size; i++) - IM_DELETE(g.Windows[i]); - g.Windows.clear(); + g.Windows.clear_delete(); g.WindowsFocusOrder.clear(); g.WindowsTempSortBuffer.clear(); g.CurrentWindow = NULL; g.CurrentWindowStack.clear(); g.WindowsById.Clear(); g.NavWindow = NULL; - g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; g.MovingWindow = NULL; g.ColorStack.clear(); @@ -4294,16 +4385,14 @@ void ImGui::Shutdown(ImGuiContext* context) g.BeginPopupStack.clear(); g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; - for (int i = 0; i < g.Viewports.Size; i++) - IM_DELETE(g.Viewports[i]); - g.Viewports.clear(); + g.Viewports.clear_delete(); g.TabBars.Clear(); g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); g.Tables.Clear(); - g.CurrentTableStack.clear(); + g.TablesTempDataStack.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); g.ClipboardHandlerData.clear(); @@ -4516,15 +4605,15 @@ static void ImGui::EndFrameDrawDimmedBackgrounds() // Choose a draw list that will be front-most across all our children // In the unlikely case that the window wasn't made active we can't rely on its drawlist and skip rendering all-together. ImGuiWindow* window = g.NavWindowingTargetAnim; - ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindow)->DrawList; + ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindowDockTree)->DrawList; draw_list->PushClipRectFullScreen(); // Docking: draw modal whitening background on other nodes of a same dock tree // For CTRL+TAB within a docking node we need to render the dimming background in 8 steps // (Because the root node renders the background in one shot, in order to avoid flickering when a child dock node is not submitted) - if (window->RootWindowDockStop->DockIsActive) - if (window->RootWindow != window->RootWindowDockStop) - RenderRectFilledWithHole(draw_list, window->RootWindow->Rect(), window->RootWindowDockStop->Rect(), GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio), g.Style.WindowRounding); + if (window->RootWindow->DockIsActive) + if (window->RootWindowDockTree != window->RootWindow) + RenderRectFilledWithHole(draw_list, window->RootWindowDockTree->Rect(), window->RootWindow->Rect(), GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio), g.Style.WindowRounding); // Draw navigation selection/windowing rectangle border float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); @@ -4535,7 +4624,7 @@ static void ImGui::EndFrameDrawDimmedBackgrounds() bb.Expand(-g.FontSize - 1.0f); rounding = window->WindowRounding; } - draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, 0, 3.0f); draw_list->PopClipRect(); } } @@ -4633,6 +4722,9 @@ void ImGui::EndFrame() CallContextHooks(&g, ImGuiContextHookType_EndFramePost); } +// Prepare the data for rendering so you can call GetDrawData() +// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all: +// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend) void ImGui::Render() { ImGuiContext& g = *GImGui; @@ -4656,11 +4748,12 @@ void ImGui::Render() // Add ImDrawList to render ImGuiWindow* windows_to_render_top_most[2]; - windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; + windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindowDockTree : NULL; windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL); for (int n = 0; n != g.Windows.Size; n++) { ImGuiWindow* window = g.Windows[n]; + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1]) AddRootWindowToDrawData(window); } @@ -4748,10 +4841,11 @@ static void FindHoveredWindow() hovered_window = g.MovingWindow; ImVec2 padding_regular = g.Style.TouchExtraPadding; - ImVec2 padding_for_resize_from_edges = g.IO.ConfigWindowsResizeFromEdges ? ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS)) : padding_regular; + ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; for (int i = g.Windows.Size - 1; i >= 0; i--) { ImGuiWindow* window = g.Windows[i]; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. if (!window->Active || window->Hidden) continue; if (window->Flags & ImGuiWindowFlags_NoMouseInputs) @@ -4765,7 +4859,7 @@ static void FindHoveredWindow() if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) bb.Expand(padding_regular); else - bb.Expand(padding_for_resize_from_edges); + bb.Expand(padding_for_resize); if (!bb.Contains(g.IO.MousePos)) continue; @@ -4781,14 +4875,14 @@ static void FindHoveredWindow() if (hovered_window == NULL) hovered_window = window; - if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow)) + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)) hovered_window_ignoring_moving_window = window; if (hovered_window && hovered_window_ignoring_moving_window) break; } g.HoveredWindow = hovered_window; - g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window; if (g.MovingWindow) @@ -5026,10 +5120,7 @@ bool ImGui::IsItemActive() { ImGuiContext& g = *GImGui; if (g.ActiveId) - { - ImGuiWindow* window = g.CurrentWindow; - return g.ActiveId == window->DC.LastItemId; - } + return g.ActiveId == g.LastItemData.ID; return false; } @@ -5037,21 +5128,17 @@ bool ImGui::IsItemActivated() { ImGuiContext& g = *GImGui; if (g.ActiveId) - { - ImGuiWindow* window = g.CurrentWindow; - if (g.ActiveId == window->DC.LastItemId && g.ActiveIdPreviousFrame != window->DC.LastItemId) + if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID) return true; - } return false; } bool ImGui::IsItemDeactivated() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDeactivated) - return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; + return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); } bool ImGui::IsItemDeactivatedAfterEdit() @@ -5064,19 +5151,20 @@ bool ImGui::IsItemDeactivatedAfterEdit() bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (g.NavId != window->DC.LastItemId || g.NavId == 0) + if (g.NavId != g.LastItemData.ID || g.NavId == 0) return false; // Special handling for the dummy item after Begin() which represent the title bar or tab. // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. - if (window->DC.LastItemId == window->ID && window->WriteAccessed) + ImGuiWindow* window = g.CurrentWindow; + if (g.LastItemData.ID == window->ID && window->WriteAccessed) return false; return true; } +// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! +// Most widgets have specific reactions based on mouse-up/down state, mouse position etc. bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button) { return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None); @@ -5085,13 +5173,13 @@ bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button) bool ImGui::IsItemToggledOpen() { ImGuiContext& g = *GImGui; - return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } bool ImGui::IsItemToggledSelection() { ImGuiContext& g = *GImGui; - return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; } bool ImGui::IsAnyItemHovered() @@ -5114,14 +5202,14 @@ bool ImGui::IsAnyItemFocused() bool ImGui::IsItemVisible() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ClipRect.Overlaps(window->DC.LastItemRect); + ImGuiContext& g = *GImGui; + return g.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect); } bool ImGui::IsItemEdited() { - ImGuiWindow* window = GetCurrentWindowRead(); - return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0; + ImGuiContext& g = *GImGui; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0; } // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. @@ -5129,7 +5217,7 @@ bool ImGui::IsItemEdited() void ImGui::SetItemAllowOverlap() { ImGuiContext& g = *GImGui; - ImGuiID id = g.CurrentWindow->DC.LastItemId; + ImGuiID id = g.LastItemData.ID; if (g.HoveredId == id) g.HoveredIdAllowOverlap = true; if (g.ActiveId == id) @@ -5139,29 +5227,39 @@ void ImGui::SetItemAllowOverlap() void ImGui::SetItemUsingMouseWheel() { ImGuiContext& g = *GImGui; - ImGuiID id = g.CurrentWindow->DC.LastItemId; + ImGuiID id = g.LastItemData.ID; if (g.HoveredId == id) g.HoveredIdUsingMouseWheel = true; if (g.ActiveId == id) g.ActiveIdUsingMouseWheel = true; } +void ImGui::SetActiveIdUsingNavAndKeys() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId != 0); + g.ActiveIdUsingNavDirMask = ~(ImU32)0; + g.ActiveIdUsingNavInputMask = ~(ImU32)0; + g.ActiveIdUsingKeyInputMask = ~(ImU64)0; + NavMoveRequestCancel(); +} + ImVec2 ImGui::GetItemRectMin() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Min; + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.Min; } ImVec2 ImGui::GetItemRectMax() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Max; + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.Max; } ImVec2 ImGui::GetItemRectSize() { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.GetSize(); + ImGuiContext& g = *GImGui; + return g.LastItemData.Rect.GetSize(); } bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) @@ -5204,7 +5302,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, b parent_window->DC.CursorPos = child_window->Pos; // Process navigation-in immediately so NavInit can run on first frame - if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll)) + if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavHasScroll)) { FocusWindow(child_window); NavInitWindow(child_window, false); @@ -5251,13 +5349,13 @@ void ImGui::EndChild() ImGuiWindow* parent_window = g.CurrentWindow; ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); ItemSize(sz); - if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + if ((window->DC.NavLayersActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) { ItemAdd(bb, window->ChildId); RenderNavHighlight(bb, window->ChildId); // When browsing a window that has no activable items (scroll only) we keep a highlight on the child - if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) + if (window->DC.NavLayersActiveMask == 0 && window == g.NavWindow) RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); } else @@ -5265,6 +5363,8 @@ void ImGui::EndChild() // Not navigable into ItemAdd(bb, 0); } + if (g.HoveredWindow == window) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; } g.WithinEndChild = false; g.LogLinePosY = -FLT_MAX; // To enforce a carriage return @@ -5367,7 +5467,12 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); } - g.WindowsFocusOrder.push_back(window); + if (!(flags & ImGuiWindowFlags_ChildWindow)) + { + g.WindowsFocusOrder.push_back(window); + window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); + } + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) g.Windows.push_front(window); // Quite slow but rare and only once else @@ -5385,9 +5490,10 @@ static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window) return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window; } -static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired) { ImGuiContext& g = *GImGui; + ImVec2 new_size = size_desired; if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) { // Using -1,-1 on either X/Y axis to preserve the current size. @@ -5412,8 +5518,9 @@ static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) { ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window); + const float decoration_up_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight(); new_size = ImMax(new_size, g.Style.WindowMinSize); - new_size.y = ImMax(new_size.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + new_size.y = ImMax(new_size.y, decoration_up_height + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows } return new_size; } @@ -5442,9 +5549,9 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; - ImVec2 size_decorations = ImVec2(0.0f, window->TitleBarHeight() + window->MenuBarHeight()); + const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); ImVec2 size_pad = window->WindowPadding * 2.0f; - ImVec2 size_desired = size_contents + size_pad + size_decorations; + ImVec2 size_desired = size_contents + size_pad + ImVec2(0.0f, decoration_up_height); if (window->Flags & ImGuiWindowFlags_Tooltip) { // Tooltip always resize @@ -5471,8 +5578,8 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); - bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - size_decorations.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); - bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - size_decorations.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - 0.0f < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_up_height < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); if (will_have_scrollbar_x) size_auto_fit.y += style.ScrollbarSize; if (will_have_scrollbar_y) @@ -5514,53 +5621,64 @@ static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& co *out_size = size_constrained; } +// Data for resizing from corner struct ImGuiResizeGripDef { ImVec2 CornerPosN; ImVec2 InnerDir; int AngleMin12, AngleMax12; }; - static const ImGuiResizeGripDef resize_grip_def[4] = { - { ImVec2(1, 1), ImVec2(-1, -1), 0, 3 }, // Lower-right - { ImVec2(0, 1), ImVec2(+1, -1), 3, 6 }, // Lower-left - { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 }, // Upper-left (Unused) - { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 }, // Upper-right (Unused) + { ImVec2(1, 1), ImVec2(-1, -1), 0, 3 }, // Lower-right + { ImVec2(0, 1), ImVec2(+1, -1), 3, 6 }, // Lower-left + { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 }, // Upper-left (Unused) + { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 } // Upper-right (Unused) }; +// Data for resizing from borders struct ImGuiResizeBorderDef { ImVec2 InnerDir; - ImVec2 CornerPosN1, CornerPosN2; + ImVec2 SegmentN1, SegmentN2; float OuterAngle; }; - static const ImGuiResizeBorderDef resize_border_def[4] = { - { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Top + { ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f }, // Left { ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f }, // Right - { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f }, // Bottom - { ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f } // Left + { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Up + { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f } // Down }; static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) { ImRect rect = window->Rect(); - if (thickness == 0.0f) rect.Max -= ImVec2(1, 1); - if (border_n == 0) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } // Top - if (border_n == 1) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } // Right - if (border_n == 2) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } // Bottom - if (border_n == 3) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } // Left + if (thickness == 0.0f) + rect.Max -= ImVec2(1, 1); + if (border_n == ImGuiDir_Left) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Right) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Up) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } + if (border_n == ImGuiDir_Down) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } IM_ASSERT(0); return ImRect(); } // 0..3: corners (Lower-right, Lower-left, Unused, Unused) -// 4..7: borders (Top, Right, Bottom, Left) -ImGuiID ImGui::GetWindowResizeID(ImGuiWindow* window, int n) +ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) { - IM_ASSERT(n >= 0 && n <= 7); + IM_ASSERT(n >= 0 && n < 4); + ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID; + id = ImHashStr("#RESIZE", 0, id); + id = ImHashData(&n, sizeof(int), id); + return id; +} + +// Borders (Left, Right, Up, Down) +ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) +{ + IM_ASSERT(dir >= 0 && dir < 4); + int n = (int)dir + 4; ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID; id = ImHashStr("#RESIZE", 0, id); id = ImHashData(&n, sizeof(int), id); @@ -5583,7 +5701,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0; const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f); - const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS : 0.0f; + const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; ImVec2 pos_target(FLT_MAX, FLT_MAX); ImVec2 size_target(FLT_MAX, FLT_MAX); @@ -5605,15 +5723,16 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s PushID("#RESIZE"); for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) { - const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; - const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); + const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN); // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window - ImRect resize_rect(corner - grip.InnerDir * grip_hover_outer_size, corner + grip.InnerDir * grip_hover_inner_size); + bool hovered, held; + ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size, corner + def.InnerDir * grip_hover_inner_size); if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x); if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y); - bool hovered, held; - ButtonBehavior(resize_rect, window->GetID(resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID() + ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); if (hovered || held) g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; @@ -5629,39 +5748,41 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s { // Resize from any of the four corners // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position - ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(grip.InnerDir * grip_hover_outer_size, grip.InnerDir * -grip_hover_inner_size, grip.CornerPosN); // Corner of the window corresponding to our corner grip - ImVec2 clamp_min = ImVec2(grip.CornerPosN.x == 1.0f ? visibility_rect.Min.x : -FLT_MAX, grip.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX); - ImVec2 clamp_max = ImVec2(grip.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX, grip.CornerPosN.y == 0.0f ? visibility_rect.Max.y : +FLT_MAX); + ImVec2 clamp_min = ImVec2(def.CornerPosN.x == 1.0f ? visibility_rect.Min.x : -FLT_MAX, def.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX, def.CornerPosN.y == 0.0f ? visibility_rect.Max.y : +FLT_MAX); + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size, def.CornerPosN); // Corner of the window corresponding to our corner grip corner_target = ImClamp(corner_target, clamp_min, clamp_max); - CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPosN, &pos_target, &size_target); + CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target); } + + // Only lower-left grip is visible before hovering/activating if (resize_grip_n == 0 || held || hovered) resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); } for (int border_n = 0; border_n < resize_border_count; border_n++) { + const ImGuiResizeBorderDef& def = resize_border_def[border_n]; + const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + bool hovered, held; - ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); - ButtonBehavior(border_rect, window->GetID(border_n + 4), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING); + ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() + ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren); //GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255)); if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held) { - g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; if (held) *border_held = border_n; } if (held) { + ImVec2 clamp_min(border_n == ImGuiDir_Right ? visibility_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max(border_n == ImGuiDir_Left ? visibility_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? visibility_rect.Max.y : +FLT_MAX); ImVec2 border_target = window->Pos; - ImVec2 border_posn; - if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Top - if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Right - if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Bottom - if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Left - ImVec2 clamp_min = ImVec2(border_n == 1 ? visibility_rect.Min.x : -FLT_MAX, border_n == 2 ? visibility_rect.Min.y : -FLT_MAX); - ImVec2 clamp_max = ImVec2(border_n == 3 ? visibility_rect.Max.x : +FLT_MAX, border_n == 0 ? visibility_rect.Max.y : +FLT_MAX); + border_target[axis] = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING; border_target = ImClamp(border_target, clamp_min, clamp_max); - CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); } } PopID(); @@ -5670,12 +5791,12 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s window->DC.NavLayerCurrent = ImGuiNavLayer_Main; // Navigation resize (keyboard/gamepad) - if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window) { ImVec2 nav_resize_delta; - if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) + if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); - if (g.NavInputSource == ImGuiInputSource_NavGamepad) + if (g.NavInputSource == ImGuiInputSource_Gamepad) nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) { @@ -5721,16 +5842,16 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) float rounding = window->WindowRounding; float border_size = window->WindowBorderSize; if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground)) - window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); int border_held = window->ResizeBorderHeld; if (border_held != -1) { const ImGuiResizeBorderDef& def = resize_border_def[border_held]; ImRect border_r = GetResizeBorderRect(window, border_held, rounding, 0.0f); - window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.CornerPosN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); - window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.CornerPosN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); - window->DrawList->PathStroke(GetColorU32(ImGuiCol_SeparatorActive), false, ImMax(2.0f, border_size)); // Thicker than usual + window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); + window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_SeparatorActive), 0, ImMax(2.0f, border_size)); // Thicker than usual } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { @@ -5800,7 +5921,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (override_alpha) bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); } - window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); } // Title bar @@ -5809,7 +5930,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); } // Menu bar @@ -5817,7 +5938,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } @@ -5868,7 +5989,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar } // Render title text, collapse button, close button -// When inside a dock node, this is handled in DockNodeUpdateTabBar() instead. +// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead. void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open) { ImGuiContext& g = *GImGui; @@ -5879,8 +6000,8 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None); // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer) - const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; // Layout buttons @@ -5917,12 +6038,11 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl *p_open = false; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; - window->DC.ItemFlags = item_flags_backup; + g.CurrentItemFlags = item_flags_backup; // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code.. - const char* UNSAVED_DOCUMENT_MARKER = "*"; - const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f; + const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f; const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button, @@ -5941,26 +6061,31 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y); ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y); - //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] - //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] - RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); if (flags & ImGuiWindowFlags_UnsavedDocument) { - ImVec2 marker_pos = ImVec2(ImMax(layout_r.Min.x, layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, layout_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f); - ImVec2 off = ImVec2(0.0f, IM_FLOOR(-g.FontSize * 0.25f)); - RenderTextClipped(marker_pos + off, layout_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_r); + ImVec2 marker_pos; + marker_pos.x = ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x); + marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f; + if (marker_pos.x > layout_r.Min.x) + { + RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text)); + clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f)); + } } + //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); } void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) { window->ParentWindow = parent_window; - window->RootWindow = window->RootWindowDockStop = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + window->RootWindow = window->RootWindowDockTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) { - window->RootWindow = parent_window->RootWindow; + window->RootWindowDockTree = parent_window->RootWindowDockTree; if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost)) - window->RootWindowDockStop = parent_window->RootWindowDockStop; + window->RootWindow = parent_window->RootWindow; } if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; @@ -6003,22 +6128,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow); - // Update the Appearing flag - bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on - const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0); + // Update the Appearing flag (note: the BeginDocked() path may also set this to true later) + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed window_just_activated_by_user |= (window != popup_ref.Window); } - window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); // Update Flags, LastFrameActive, BeginOrderXXX fields + const bool window_was_appearing = window->Appearing; if (first_begin_of_the_frame) { + window->Appearing = window_just_activated_by_user; + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + window->FlagsPreviousFrame = window->Flags; window->Flags = (ImGuiWindowFlags)flags; window->LastFrameActive = current_frame; @@ -6040,6 +6166,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL); bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window); + bool dock_node_was_visible = window->DockNodeIsVisible; + bool dock_tab_was_visible = window->DockTabIsVisible; if (has_dock_node || new_auto_dock_node) { BeginDocked(window, p_open); @@ -6049,11 +6177,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Docking currently override constraints g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; + + // Amend the Appearing flag + if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing) + { + window->Appearing = true; + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + } + } + else + { + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; } } // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack - ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); @@ -6063,7 +6202,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Add to stack // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() - g.CurrentWindowStack.push_back(window); + ImGuiWindowStackData window_stack_data; + window_stack_data.Window = window; + window_stack_data.ParentLastItemDataBackup = g.LastItemData; + g.CurrentWindowStack.push_back(window_stack_data); g.CurrentWindow = window; window->DC.StackSizesOnBegin.SetToCurrentState(); g.CurrentWindow = NULL; @@ -6076,9 +6218,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->PopupId = popup_ref.PopupId; } - if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) - window->NavLastIds[0] = 0; - // Update ->RootWindow and others pointers (before any possible call to FocusWindow) if (first_begin_of_the_frame) UpdateWindowParentAndRootLinks(window, flags, parent_window); @@ -6168,7 +6307,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS // Update contents size from last frame for auto-fitting (or use explicit size) + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0); CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal); + + // FIXME: These flags are decremented before they are used. This means that in order to have these fields produce their intended behaviors + // for one frame we must set them to at least 2, which is counter-intuitive. HiddenFramesCannotSkipItems is a more complicated case because + // it has a single usage before this code block and may be set below before it is finally checked. if (window->HiddenFramesCanSkipItems > 0) window->HiddenFramesCanSkipItems--; if (window->HiddenFramesCannotSkipItems > 0) @@ -6198,7 +6342,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // SELECT VIEWPORT // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes. - UpdateSelectWindowViewport(window); + WindowSelectViewport(window); SetCurrentViewport(window, window->Viewport); window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; SetCurrentWindow(window); @@ -6232,7 +6376,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { window->Collapsed = !window->Collapsed; MarkIniSettingsDirty(window); - FocusWindow(window); } } else @@ -6331,83 +6474,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetCurrentWindow(window); } - bool viewport_rect_changed = false; if (window->ViewportOwned) - { - // Synchronize window --> viewport in most situations - // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM - if (window->Viewport->PlatformRequestMove) - { - window->Pos = window->Viewport->Pos; - MarkIniSettingsDirty(window); - } - else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0) - { - viewport_rect_changed = true; - window->Viewport->Pos = window->Pos; - } - - if (window->Viewport->PlatformRequestResize) - { - window->Size = window->SizeFull = window->Viewport->Size; - MarkIniSettingsDirty(window); - } - else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0) - { - viewport_rect_changed = true; - window->Viewport->Size = window->Size; - } - window->Viewport->UpdateWorkRect(); - - // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() - // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. - if (viewport_rect_changed) - UpdateViewportPlatformMonitor(window->Viewport); - - // Update common viewport flags - const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; - ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; - const bool is_modal = (flags & ImGuiWindowFlags_Modal) != 0; - const bool is_short_lived_floating_window = (flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; - if (flags & ImGuiWindowFlags_Tooltip) - viewport_flags |= ImGuiViewportFlags_TopMost; - if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal) - viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; - if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) - viewport_flags |= ImGuiViewportFlags_NoDecoration; - - // Not correct to set modal as topmost because: - // - Because other popups can be stacked above a modal (e.g. combo box in a modal) - // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" - //if (flags & ImGuiWindowFlags_Modal) - // viewport_flags |= ImGuiViewportFlags_TopMost; - - // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them - // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). - // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, - // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. - if (is_short_lived_floating_window && !is_modal) - viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; - - // We can overwrite viewport flags using ImGuiWindowClass (advanced users) - // We don't default to the main viewport because. - if (window->WindowClass.ParentViewportId) - window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId; - else if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack) - window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID; - else - window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; - if (window->WindowClass.ViewportFlagsOverrideSet) - viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; - if (window->WindowClass.ViewportFlagsOverrideClear) - viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; - - // We also tell the backend that clearing the platform window won't be necessary, as our window is filling the viewport and we have disabled BgAlpha - if (!(flags & ImGuiWindowFlags_NoBackground)) - viewport_flags &= ~ImGuiViewportFlags_NoRendererClear; - - window->Viewport->Flags = viewport_flags; - } + WindowSyncOwnedViewport(window, parent_window_in_stack); // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. @@ -6427,19 +6495,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0) { - if (window->Viewport->PlatformMonitor == -1) - { - // Fallback for "lost" window (e.g. a monitor disconnected): we move the window back over the main viewport - const ImGuiViewport* main_viewport = GetMainViewport(); - SetWindowPos(window, main_viewport->Pos + style.DisplayWindowPadding, ImGuiCond_Always); - } - else - { - ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->Viewport->PlatformMonitor]; - visibility_rect.Min = monitor.WorkPos + visibility_padding; - visibility_rect.Max = monitor.WorkPos + monitor.WorkSize - visibility_padding; - ClampWindowRect(window, visibility_rect); - } + // Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka the main viewport. + const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(window->Viewport); + visibility_rect.Min = monitor->WorkPos + visibility_padding; + visibility_rect.Max = monitor->WorkPos + monitor->WorkSize - visibility_padding; + ClampWindowRect(window, visibility_rect); } } window->Pos = ImFloor(window->Pos); @@ -6582,7 +6642,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Draw modal or window list full viewport dimming background (for other viewports we'll render them in EndFrame) ImGuiWindow* window_window_list = g.NavWindowingListWindow; const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetTopMostPopupModal() && window->HiddenFramesCannotSkipItems <= 0; - const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && ((window == g.NavWindowingTargetAnim->RootWindow) || (window == window_window_list && window_window_list->Viewport != g.NavWindowingTargetAnim->Viewport)); + const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && ((window == g.NavWindowingTargetAnim->RootWindowDockTree) || (window == window_window_list && window_window_list->Viewport != g.NavWindowingTargetAnim->Viewport)); if (dim_bg_for_modal || dim_bg_for_window_list) { const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); @@ -6633,7 +6693,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) bb.Expand(-g.FontSize - 1.0f); rounding = window->WindowRounding; } - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, 0, 3.0f); } // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING) @@ -6676,13 +6736,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; - window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; - window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext; + window->DC.NavLayersActiveMaskNext = 0x00; window->DC.NavHideHighlightOneFrame = false; window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f); window->DC.MenuBarAppending = false; - window->DC.MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; window->DC.TreeJumpToParentOnPopMask = 0x00; window->DC.ChildWindows.resize(0); @@ -6706,7 +6766,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (want_focus) { FocusWindow(window); - NavInitWindow(window, false); + NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls } // Close requested by platform window @@ -6742,27 +6802,27 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source. // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it. - if ((g.MovingWindow == window) && (g.IO.ConfigDockingWithShift == g.IO.KeyShift)) - if ((window->RootWindow->Flags & ImGuiWindowFlags_NoDocking) == 0) + if (g.MovingWindow == window && g.IO.KeyShift == false) + if ((window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0) BeginDockableDragDropSource(window); // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead. if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking)) - if (g.MovingWindow == NULL || g.MovingWindow->RootWindow != window) - if ((window == window->RootWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost)) + if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window) + if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost)) BeginDockableDragDropTarget(window); } // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. if (window->DockIsActive) - SetLastItemData(window, window->ID, window->DockTabItemStatusFlags, window->DockTabItemRect); + SetLastItemData(window->ID, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect); else - SetLastItemData(window, window->MoveId, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) - IMGUI_TEST_ENGINE_ITEM_ADD(window->DC.LastItemRect, window->DC.LastItemId); + IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); #endif } else @@ -6773,8 +6833,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Pull/inherit current state - window->DC.ItemFlags = g.ItemFlagsStack.back(); // Inherit from shared stack - window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // Inherit from parent only // -V595 + window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : window->GetID("#FOCUSSCOPE"); // Inherit from parent only // -V595 if (!(flags & ImGuiWindowFlags_DockNodeHost)) PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); @@ -6823,12 +6882,25 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Update the Hidden flag window->Hidden = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0) || (window->HiddenFramesForRenderOnly > 0); + // Disable inputs for requested number of frames + if (window->DisableInputsFrames > 0) + { + window->DisableInputsFrames--; + window->Flags |= ImGuiWindowFlags_NoInputs; + } + // Update the SkipItems flag, used to early out of all items functions (no layout required) bool skip_items = false; if (window->Collapsed || !window->Active || window->Hidden) if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0) skip_items = true; window->SkipItems = skip_items; + + // Sanity check: there are two spots which can set Appearing = true + // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false + // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert. + if (window->SkipItems && !window->Appearing) + IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177 } return !window->SkipItems; @@ -6867,11 +6939,12 @@ void ImGui::End() host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding; // Pop from window stack + g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; g.CurrentWindowStack.pop_back(); if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); window->DC.StackSizesOnBegin.CompareWithCurrentState(); - SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); + SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); if (g.CurrentWindow) SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); } @@ -6879,22 +6952,29 @@ void ImGui::End() void ImGui::BringWindowToFocusFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; + IM_ASSERT(window == window->RootWindow); + + const int cur_order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); if (g.WindowsFocusOrder.back() == window) return; - for (int i = g.WindowsFocusOrder.Size - 2; i >= 0; i--) // We can ignore the top-most window - if (g.WindowsFocusOrder[i] == window) - { - memmove(&g.WindowsFocusOrder[i], &g.WindowsFocusOrder[i + 1], (size_t)(g.WindowsFocusOrder.Size - i - 1) * sizeof(ImGuiWindow*)); - g.WindowsFocusOrder[g.WindowsFocusOrder.Size - 1] = window; - break; - } + + const int new_order = g.WindowsFocusOrder.Size - 1; + for (int n = cur_order; n < new_order; n++) + { + g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; + g.WindowsFocusOrder[n]->FocusOrder--; + IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + } + g.WindowsFocusOrder[new_order] = window; + window->FocusOrder = (short)new_order; } void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindow* current_front_window = g.Windows.back(); - if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + if (current_front_window == window || current_front_window->RootWindowDockTree == window) // Cheap early out (could be better) return; for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window if (g.Windows[i] == window) @@ -6929,11 +7009,12 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavWindow = window; if (window && g.NavDisableMouseHover) g.NavMousePosDirty = true; - g.NavInitRequest = false; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavFocusScopeId = 0; g.NavIdIsAlive = false; g.NavLayer = ImGuiNavLayer_Main; + g.NavInitRequest = g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL); } @@ -6941,9 +7022,9 @@ void ImGui::FocusWindow(ImGuiWindow* window) ClosePopupsOverWindow(window, false); // Move the root window to the top of the pile - IM_ASSERT(window == NULL || window->RootWindow != NULL); - ImGuiWindow* focus_front_window = window ? window->RootWindowDockStop : NULL; - ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; + IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL); + ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; + ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL; ImGuiDockNode* dock_node = window ? window->DockNode : NULL; bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow); @@ -6951,7 +7032,7 @@ void ImGui::FocusWindow(ImGuiWindow* window) // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the child window. - if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindowDockStop != focus_front_window) + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host) ClearActiveID(); @@ -6974,18 +7055,13 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind { ImGuiContext& g = *GImGui; - int start_idx = g.WindowsFocusOrder.Size - 1; - if (under_this_window != NULL) - { - int under_this_window_idx = FindWindowFocusIndex(under_this_window); - if (under_this_window_idx != -1) - start_idx = under_this_window_idx - 1; - } + const int start_idx = ((under_this_window != NULL) ? FindWindowFocusIndex(under_this_window) : g.WindowsFocusOrder.Size) - 1; for (int i = start_idx; i >= 0; i--) { // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. ImGuiWindow* window = g.WindowsFocusOrder[i]; - if (window != ignore_window && window->WasActive && window->RootWindowDockStop == window) + IM_ASSERT(window == window->RootWindow); + if (window != ignore_window && window->WasActive) if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { // FIXME-DOCK: This is failing (lagging by one frame) for docked windows. @@ -7000,6 +7076,7 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind FocusWindow(NULL); } +// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. void ImGui::SetCurrentFont(ImFont* font) { ImGuiContext& g = *GImGui; @@ -7037,24 +7114,47 @@ void ImGui::PopFont() void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiItemFlags item_flags = window->DC.ItemFlags; + ImGuiItemFlags item_flags = g.CurrentItemFlags; IM_ASSERT(item_flags == g.ItemFlagsStack.back()); if (enabled) item_flags |= option; else item_flags &= ~option; - window->DC.ItemFlags = item_flags; + g.CurrentItemFlags = item_flags; g.ItemFlagsStack.push_back(item_flags); } void ImGui::PopItemFlag() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack. g.ItemFlagsStack.pop_back(); - window->DC.ItemFlags = g.ItemFlagsStack.back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); +} + +// PushDisabled()/PopDisabled() +// - Those can be nested but this cannot be used to enable an already disabled section (a single PushDisabled(true) in the stack is enough to keep things disabled) +// - Those are not yet exposed in imgui.h because we are unsure of how to alter the style in a way that works for everyone. +// We may rework this. Hypothetically, a future styling system may set a flag which make widgets use different colors. +// - Feedback welcome at https://github.com/ocornut/imgui/issues/211 +// - You may trivially implement your own variation of this if needed. +// Here we test (CurrentItemFlags & ImGuiItemFlags_Disabled) to allow nested PushDisabled() calls. +void ImGui::PushDisabled(bool disabled) +{ + ImGuiContext& g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (!was_disabled && disabled) + PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.6f); + PushItemFlag(ImGuiItemFlags_Disabled, was_disabled || disabled); +} + +void ImGui::PopDisabled() +{ + ImGuiContext& g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + PopItemFlag(); + if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) + PopStyleVar(); } // FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system. @@ -7092,9 +7192,10 @@ void ImGui::PopTextWrapPos() window->DC.TextWrapPosStack.pop_back(); } +// FIXME: We are exposing the docking hierarchy to end-user here (via IsWindowHovered, IsWindowFocused) which is unusual. bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) { - if (window->RootWindow == potential_parent) + if (window->RootWindowDockTree == potential_parent) return true; while (window != NULL) { @@ -7123,30 +7224,28 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function ImGuiContext& g = *GImGui; + if (g.HoveredWindow == NULL) + return false; - if (flags & ImGuiHoveredFlags_AnyWindow) - { - if (g.HoveredWindow == NULL) - return false; - } - else + if ((flags & ImGuiHoveredFlags_AnyWindow) == 0) { + ImGuiWindow* window = g.CurrentWindow; switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) { case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: - if (g.HoveredWindow == NULL || g.HoveredWindow->RootWindowDockStop != g.CurrentWindow->RootWindowDockStop) + if (g.HoveredWindow->RootWindow != window->RootWindow) return false; break; case ImGuiHoveredFlags_RootWindow: - if (g.HoveredWindow != g.CurrentWindow->RootWindowDockStop) + if (g.HoveredWindow != window->RootWindow) return false; break; case ImGuiHoveredFlags_ChildWindows: - if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + if (!IsWindowChildOf(g.HoveredWindow, window)) return false; break; default: - if (g.HoveredWindow != g.CurrentWindow) + if (g.HoveredWindow != window) return false; break; } @@ -7171,9 +7270,9 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) { case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: - return g.NavWindow && g.NavWindow->RootWindowDockStop == g.CurrentWindow->RootWindowDockStop; + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; case ImGuiFocusedFlags_RootWindow: - return g.NavWindow == g.CurrentWindow->RootWindowDockStop; + return g.NavWindow == g.CurrentWindow->RootWindow; case ImGuiFocusedFlags_ChildWindows: return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); default: @@ -7198,7 +7297,7 @@ bool ImGui::IsWindowDocked() // If you want a window to never be focused, you may use the e.g. NoInputs flag. bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) { - return window->WasActive && window == window->RootWindowDockStop && !(window->Flags & ImGuiWindowFlags_NoNavFocus); + return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); } float ImGui::GetWindowWidth() @@ -7521,9 +7620,9 @@ void ImGui::SetKeyboardFocusHere(int offset) IM_ASSERT(offset >= -1); // -1 is allowed but not below ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - g.FocusRequestNextWindow = window; - g.FocusRequestNextCounterRegular = window->DC.FocusCounterRegular + 1 + offset; - g.FocusRequestNextCounterTabStop = INT_MAX; + g.TabFocusRequestNextWindow = window; + g.TabFocusRequestNextCounterRegular = window->DC.FocusCounterRegular + 1 + offset; + g.TabFocusRequestNextCounterTabStop = INT_MAX; } void ImGui::SetItemDefaultFocus() @@ -7532,11 +7631,11 @@ void ImGui::SetItemDefaultFocus() ImGuiWindow* window = g.CurrentWindow; if (!window->Appearing) return; - if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == window->DC.NavLayerCurrent) { g.NavInitRequest = false; - g.NavInitResultId = g.NavWindow->DC.LastItemId; - g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); + g.NavInitResultId = g.LastItemData.ID; + g.NavInitResultRectRel = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); NavUpdateAnyRequestFlag(); if (!IsItemVisible()) SetScrollHereY(); @@ -7674,7 +7773,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() ImGuiContext& g = *GImGui; // Check user IM_ASSERT macro - // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means you assert macro is incorrectly defined! + // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined! // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block. // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.) // #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong! @@ -7687,10 +7786,9 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); - IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); + IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); - IM_ASSERT(g.Style.CircleSegmentMaxError > 0.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); @@ -7793,13 +7891,11 @@ void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, voi ImGuiContext& g = *GImGui; while (g.CurrentWindowStack.Size > 0) { -#ifdef IMGUI_HAS_TABLE while (g.CurrentTable && (g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow)) { if (log_callback) log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name); EndTable(); } -#endif ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(window != NULL); while (g.CurrentTabBar != NULL) //-V1044 @@ -7969,14 +8065,21 @@ void ImGui::ItemSize(const ImRect& bb, float text_baseline_y) // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. -bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemAddFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Set item data + g.LastItemData.ID = id; + g.LastItemData.Rect = bb; + g.LastItemData.InFlags = g.CurrentItemFlags; + g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; + + // Directional navigation processing if (id != 0) { - // Navigation processing runs prior to clipping early-out + // Runs prior to clipping early-out // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of @@ -7985,7 +8088,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. - window->DC.NavLayerActiveMaskNext |= (1 << window->DC.NavLayerCurrent); + window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); if (g.NavId == id || g.NavAnyRequest) if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) @@ -8000,11 +8103,6 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) } #endif } - - // Equivalent to calling SetLastItemData() - window->DC.LastItemId = id; - window->DC.LastItemRect = bb; - window->DC.LastItemStatusFlags = ImGuiItemStatusFlags_None; g.NextItemData.Flags = ImGuiNextItemDataFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -8018,9 +8116,14 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) return false; //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + // Tab stop handling (previously was using internal ItemFocusable() api) + // FIXME-NAV: We would now want to move this above the clipping test, but this would require being able to scroll and currently this would mean an extra frame. (#4079, #343) + if (flags & ImGuiItemAddFlags_Focusable) + ItemFocusable(window, id); + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (IsMouseHoveringRect(bb.Min, bb.Max)) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; return true; } @@ -8303,6 +8406,7 @@ void ImGui::BeginGroup() group_data.BackupCurrLineSize = window->DC.CurrLineSize; group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset; group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; + group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; group_data.EmitItem = true; @@ -8351,19 +8455,24 @@ void ImGui::EndGroup() const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); if (group_contains_curr_active_id) - window->DC.LastItemId = g.ActiveId; + g.LastItemData.ID = g.ActiveId; else if (group_contains_prev_active_id) - window->DC.LastItemId = g.ActiveIdPreviousFrame; - window->DC.LastItemRect = group_bb; + g.LastItemData.ID = g.ActiveIdPreviousFrame; + g.LastItemData.Rect = group_bb; + + // Forward Hovered flag + const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0; + if (group_contains_curr_hovered_id) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; // Forward Edited flag if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; // Forward Deactivated flag - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDeactivated; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Deactivated; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; g.GroupStack.pop_back(); //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] @@ -8392,24 +8501,29 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) ImVec2 scroll = window->Scroll; if (window->ScrollTarget.x < FLT_MAX) { + float decoration_total_width = window->ScrollbarSizes.x; float center_x_ratio = window->ScrollTargetCenterRatio.x; float scroll_target_x = window->ScrollTarget.x; - float snap_x_min = 0.0f; - float snap_x_max = window->ScrollMax.x + window->Size.x; if (window->ScrollTargetEdgeSnapDist.x > 0.0f) + { + float snap_x_min = 0.0f; + float snap_x_max = window->ScrollMax.x + window->SizeFull.x - decoration_total_width; scroll_target_x = CalcScrollEdgeSnap(scroll_target_x, snap_x_min, snap_x_max, window->ScrollTargetEdgeSnapDist.x, center_x_ratio); - scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - window->ScrollbarSizes.x); + } + scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - decoration_total_width); } if (window->ScrollTarget.y < FLT_MAX) { - float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); + float decoration_total_height = window->TitleBarHeight() + window->MenuBarHeight() + window->ScrollbarSizes.y; float center_y_ratio = window->ScrollTargetCenterRatio.y; float scroll_target_y = window->ScrollTarget.y; - float snap_y_min = 0.0f; - float snap_y_max = window->ScrollMax.y + window->Size.y - decoration_up_height; if (window->ScrollTargetEdgeSnapDist.y > 0.0f) + { + float snap_y_min = 0.0f; + float snap_y_max = window->ScrollMax.y + window->SizeFull.y - decoration_total_height; scroll_target_y = CalcScrollEdgeSnap(scroll_target_y, snap_y_min, snap_y_max, window->ScrollTargetEdgeSnapDist.y, center_y_ratio); - scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - window->ScrollbarSizes.y - decoration_up_height); + } + scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - decoration_total_height); } scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f)); scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f)); @@ -8522,7 +8636,8 @@ void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio) { IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); - local_y -= window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect + const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect; + local_y -= decoration_up_height; window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y); // Convert local position to scroll offset window->ScrollTargetCenterRatio.y = center_y_ratio; window->ScrollTargetEdgeSnapDist.y = 0.0f; @@ -8545,8 +8660,8 @@ void ImGui::SetScrollHereX(float center_x_ratio) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - float spacing_x = g.Style.ItemSpacing.x; - float target_pos_x = ImLerp(window->DC.LastItemRect.Min.x - spacing_x, window->DC.LastItemRect.Max.x + spacing_x, center_x_ratio); + float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x); + float target_pos_x = ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio); SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos // Tweak: snap on edges when aiming at an item very close to the edge @@ -8558,7 +8673,7 @@ void ImGui::SetScrollHereY(float center_y_ratio) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - float spacing_y = g.Style.ItemSpacing.y; + float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y); float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio); SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos @@ -8689,6 +8804,11 @@ void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) OpenPopupEx(g.CurrentWindow->GetID(str_id), popup_flags); } +void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags) +{ + OpenPopupEx(id, popup_flags); +} + // Mark popup as open (toggle toward open state). // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). @@ -8765,12 +8885,12 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3: // Window -> Popup1 -> Popup2 -> Popup3 - // - Each popups may contain child windows, which is why we compare ->RootWindow! + // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree! // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child bool ref_window_is_descendent_of_popup = false; for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window) - if (popup_window->RootWindow == ref_window->RootWindow) + if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) { ref_window_is_descendent_of_popup = true; break; @@ -8937,28 +9057,41 @@ void ImGui::EndPopup() // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) { - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) OpenPopupEx(id, popup_flags); } } // This is a helper to handle the simplest case of associating one named popup to one given widget. -// - You can pass a NULL str_id to use the identifier of the last item. -// - You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). -// - This is essentially the same as calling OpenPopupOnItemClick() + BeginPopup() but written to avoid -// computing the ID twice because BeginPopupContextXXX functions may be called very frequently. +// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id. +// - To create a popup with a specific identifier, pass it in str_id. +// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. +// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id. +// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// This is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// OpenPopupOnItemClick(str_id); +// return BeginPopup(id); +// Which is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) +// OpenPopup(id); +// return BeginPopup(id); +// The main difference being that this is tweaked to avoid computing the ID twice. bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) OpenPopupEx(id, popup_flags); @@ -8967,7 +9100,8 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "window_context"; ImGuiID id = window->GetID(str_id); @@ -8980,7 +9114,8 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags) { - ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "void_context"; ImGuiID id = window->GetID(str_id); @@ -9071,7 +9206,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s } // Note that this is used for popups, which can overlap the non work-area of individual viewports. -ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window) +ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImRect r_screen; @@ -9096,7 +9231,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) { ImGuiContext& g = *GImGui; - ImRect r_outer = GetWindowAllowedExtentRect(window); + ImRect r_outer = GetPopupAllowedExtentRect(window); if (window->Flags & ImGuiWindowFlags_ChildMenu) { // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. @@ -9135,26 +9270,19 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // [SECTION] KEYBOARD/GAMEPAD NAVIGATION //----------------------------------------------------------------------------- -// FIXME-NAV: The existence of SetNavID vs SetNavIDWithRectRel vs SetFocusID is incredibly messy and confusing, -// and needs some explanation or serious refactoring. -void ImGui::SetNavID(ImGuiID id, int nav_layer, ImGuiID focus_scope_id) +// FIXME-NAV: The existence of SetNavID vs SetFocusID properly needs to be clarified/reworked. +void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel) { ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindow); - IM_ASSERT(nav_layer == 0 || nav_layer == 1); + IM_ASSERT(g.NavWindow != NULL); + IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu); g.NavId = id; + g.NavLayer = nav_layer; g.NavFocusScopeId = focus_scope_id; g.NavWindow->NavLastIds[nav_layer] = id; -} - -void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel) -{ - ImGuiContext& g = *GImGui; - SetNavID(id, nav_layer, focus_scope_id); g.NavWindow->NavRectRel[nav_layer] = rect_rel; - g.NavMousePosDirty = true; - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; + //g.NavDisableHighlight = false; + //g.NavDisableMouseHover = g.NavMousePosDirty = true; } void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) @@ -9172,8 +9300,8 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) g.NavLayer = nav_layer; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; window->NavLastIds[nav_layer] = id; - if (window->DC.LastItemId == id) - window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + if (g.LastItemData.ID == id) + window->NavRectRel[nav_layer] = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); if (g.ActiveIdSource == ImGuiInputSource_Nav) g.NavDisableMouseHover = true; @@ -9212,7 +9340,7 @@ static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect } // Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool ImGui::NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) +static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -9270,7 +9398,7 @@ static bool ImGui::NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) else { // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) - quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; } #if IMGUI_DEBUG_NAV_SCORING @@ -9343,7 +9471,7 @@ static bool ImGui::NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) return new_best; } -static void ImGui::NavApplyItemToResult(ImGuiNavMoveResult* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel) +static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel) { result->Window = window; result->ID = id; @@ -9358,19 +9486,20 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. // return; - const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImGuiItemFlags item_flags = g.LastItemData.InFlags; const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); // Process Init Request if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) { // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + const bool candidate_for_nav_default_focus = (item_flags & (ImGuiItemFlags_NoNavDefaultFocus | ImGuiItemFlags_Disabled)) == 0; + if (candidate_for_nav_default_focus || g.NavInitResultId == 0) { g.NavInitResultId = id; g.NavInitResultRectRel = nav_bb_rel; } - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + if (candidate_for_nav_default_focus) { g.NavInitRequest = false; // Found a match, clear request NavUpdateAnyRequestFlag(); @@ -9381,7 +9510,7 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) { - ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; #if IMGUI_DEBUG_NAV_SCORING // [DEBUG] Score all items in NavWindow at all times if (!g.NavMoveRequest) @@ -9408,7 +9537,6 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con g.NavLayer = window->DC.NavLayerCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavIdIsAlive = true; - g.NavIdTabCounter = window->DC.FocusCounterTabStop; window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) } } @@ -9453,7 +9581,7 @@ void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags mov static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window) { ImGuiWindow* parent = nav_window; - while (parent && parent->RootWindowDockStop != parent && (parent->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + while (parent && parent->RootWindow != parent && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) parent = parent->ParentWindow; if (parent && parent != nav_window) parent->NavLastChildNavWindow = nav_window; @@ -9471,17 +9599,23 @@ static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) return window; } -static void NavRestoreLayer(ImGuiNavLayer layer) +void ImGui::NavRestoreLayer(ImGuiNavLayer layer) { ImGuiContext& g = *GImGui; - g.NavLayer = layer; - if (layer == 0) - g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); + if (layer == ImGuiNavLayer_Main) + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); ImGuiWindow* window = g.NavWindow; if (window->NavLastIds[layer] != 0) - ImGui::SetNavIDWithRectRel(window->NavLastIds[layer], layer, 0, g.NavWindow->NavRectRel[layer]); + { + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; + } else - ImGui::NavInitWindow(window, true); + { + g.NavLayer = layer; + NavInitWindow(window, true); + } } static inline void ImGui::NavUpdateAnyRequestFlag() @@ -9495,16 +9629,23 @@ static inline void ImGui::NavUpdateAnyRequestFlag() // This needs to be called before we submit any widget (aka in or before Begin) void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) { + // FIXME: ChildWindow test here is wrong for docking ImGuiContext& g = *GImGui; IM_ASSERT(window == g.NavWindow); + + if (window->Flags & ImGuiWindowFlags_NoNavInputs) + { + g.NavId = g.NavFocusScopeId = 0; + return; + } + bool init_for_nav = false; - if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) - if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) - init_for_nav = true; + if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); if (init_for_nav) { - SetNavID(0, g.NavLayer, 0); + SetNavID(0, g.NavLayer, 0, ImRect()); g.NavInitRequest = true; g.NavInitRequestFromMove = false; g.NavInitResultId = 0; @@ -9592,17 +9733,17 @@ static void ImGui::NavUpdate() // (do it before we map Keyboard input!) bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_NavGamepad) + if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_Gamepad) { if (io.NavInputs[ImGuiNavInput_Activate] > 0.0f || io.NavInputs[ImGuiNavInput_Input] > 0.0f || io.NavInputs[ImGuiNavInput_Cancel] > 0.0f || io.NavInputs[ImGuiNavInput_Menu] > 0.0f || io.NavInputs[ImGuiNavInput_DpadLeft] > 0.0f || io.NavInputs[ImGuiNavInput_DpadRight] > 0.0f || io.NavInputs[ImGuiNavInput_DpadUp] > 0.0f || io.NavInputs[ImGuiNavInput_DpadDown] > 0.0f) - g.NavInputSource = ImGuiInputSource_NavGamepad; + g.NavInputSource = ImGuiInputSource_Gamepad; } // Update Keyboard->Nav inputs mapping if (nav_keyboard_active) { - #define NAV_MAP_KEY(_KEY, _NAV_INPUT) do { if (IsKeyDown(io.KeyMap[_KEY])) { io.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } } while (0) + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) do { if (IsKeyDown(io.KeyMap[_KEY])) { io.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_Keyboard; } } while (0) NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); @@ -9614,8 +9755,16 @@ static void ImGui::NavUpdate() io.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; if (io.KeyShift) io.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; - if (io.KeyAlt && !io.KeyCtrl) // AltGR is Alt+Ctrl, also even on keyboards without AltGR we don't want Alt+Ctrl to open menu. + + // AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl) + // But also even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. + if (io.KeyAlt && !io.KeyCtrl) io.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; + + // We automatically cancel toggling nav layer when any text has been typed while holding Alt. (See #370) + if (io.KeyAlt && !io.KeyCtrl && g.NavWindowingToggleLayer && io.InputQueueCharacters.Size > 0) + g.NavWindowingToggleLayer = false; + #undef NAV_MAP_KEY } memcpy(io.NavInputsDownDurationPrev, io.NavInputsDownDuration, sizeof(io.NavInputsDownDuration)); @@ -9623,7 +9772,7 @@ static void ImGui::NavUpdate() io.NavInputsDownDuration[i] = (io.NavInputs[i] > 0.0f) ? (io.NavInputsDownDuration[i] < 0.0f ? 0.0f : io.NavInputsDownDuration[i] + io.DeltaTime) : -1.0f; // Process navigation init request (select first/default focus) - if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + if (g.NavInitResultId != 0) NavUpdateInitResult(); g.NavInitRequest = false; g.NavInitRequestFromMove = false; @@ -9648,13 +9797,11 @@ static void ImGui::NavUpdate() { // Set mouse position given our knowledge of the navigated item position from last frame if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) - { if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) { io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); io.WantSetMousePos = true; } - } g.NavMousePosDirty = false; } g.NavIdIsAlive = false; @@ -9683,18 +9830,20 @@ static void ImGui::NavUpdate() if (!IsActiveIdUsingNavInput(ImGuiNavInput_Cancel)) ClearActiveID(); } - else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow && g.NavWindow != g.NavWindow->RootWindowDockStop) + else if (g.NavLayer != ImGuiNavLayer_Main) + { + // Leave the "menu" layer + NavRestoreLayer(ImGuiNavLayer_Main); + } + else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) { // Exit child window ImGuiWindow* child_window = g.NavWindow; ImGuiWindow* parent_window = g.NavWindow->ParentWindow; IM_ASSERT(child_window->ChildId != 0); + ImRect child_rect = child_window->Rect(); FocusWindow(parent_window); - SetNavID(child_window->ChildId, 0, 0); - // Reassigning with same value, we're being explicit here. - g.NavIdIsAlive = false; // -V1048 - if (g.NavDisableMouseHover) - g.NavMousePosDirty = true; + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, ImRect(child_rect.Min - parent_window->Pos, child_rect.Max - parent_window->Pos)); } else if (g.OpenPopupStack.Size > 0) { @@ -9702,11 +9851,6 @@ static void ImGui::NavUpdate() if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); } - else if (g.NavLayer != ImGuiNavLayer_Main) - { - // Leave the "menu" layer - NavRestoreLayer(ImGuiNavLayer_Main); - } else { // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were @@ -9796,7 +9940,7 @@ static void ImGui::NavUpdate() // *Fallback* manual-scroll with Nav directional keys when window has no navigable item ImGuiWindow* window = g.NavWindow; const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) { if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) SetScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); @@ -9821,7 +9965,7 @@ static void ImGui::NavUpdate() // When using gamepad, we project the reference nav bounding box into window visible area. // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad every movements are relative // (can't focus a visible object like we can with the mouse). - if (g.NavMoveRequest && g.NavInputSource == ImGuiInputSource_NavGamepad && g.NavLayer == ImGuiNavLayer_Main) + if (g.NavMoveRequest && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main) { ImGuiWindow* window = g.NavWindow; ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); @@ -9862,12 +10006,14 @@ static void ImGui::NavUpdateInitResult() return; // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently. IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name); + SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); if (g.NavInitRequestFromMove) - SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); - else - SetNavID(g.NavInitResultId, g.NavLayer, 0); - g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; + } } // Apply result from previous frame navigation directional move request @@ -9886,7 +10032,7 @@ static void ImGui::NavUpdateMoveResult() } // Select which result to use - ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) @@ -9930,7 +10076,9 @@ static void ImGui::NavUpdateMoveResult() g.NavJustMovedToKeyMods = g.NavMoveRequestKeyMods; } IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name); - SetNavIDWithRectRel(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); + SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; } // Handle PageUp/PageDown/Home/End keys @@ -9951,7 +10099,7 @@ static float ImGui::NavUpdatePageUpPageDown() const bool end_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_End]) && !IsActiveIdUsingKey(ImGuiKey_End); if (page_up_held != page_down_held || home_pressed != end_pressed) // If either (not both) are pressed { - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) { // Fallback manual-scroll when window has no navigable item if (IsKeyPressed(io.KeyMap[ImGuiKey_PageUp], true)) @@ -10069,13 +10217,13 @@ static void ImGui::NavEndFrame() } } -static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) // FIXME-OPT O(N) +static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) { ImGuiContext& g = *GImGui; - for (int i = g.WindowsFocusOrder.Size - 1; i >= 0; i--) - if (g.WindowsFocusOrder[i] == window) - return i; - return -1; + IM_UNUSED(g); + int order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[order] == window); + return order; } static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) @@ -10131,15 +10279,15 @@ static void ImGui::NavUpdateWindowing() if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindowDockStop; + g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; - g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; } // Gamepad update g.NavWindowingTimer += g.IO.DeltaTime; - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad) + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad) { // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); @@ -10165,7 +10313,7 @@ static void ImGui::NavUpdateWindowing() } // Keyboard: Focus - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard) + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) { // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f @@ -10187,15 +10335,15 @@ static void ImGui::NavUpdateWindowing() if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) { ImVec2 move_delta; - if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + if (g.NavInputSource == ImGuiInputSource_Keyboard && !g.IO.KeyShift) move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); - if (g.NavInputSource == ImGuiInputSource_NavGamepad) + if (g.NavInputSource == ImGuiInputSource_Gamepad) move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); if (move_delta.x != 0.0f || move_delta.y != 0.0f) { const float NAV_MOVE_SPEED = 800.0f; const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well - ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow; + ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree; SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, ImGuiCond_Always); MarkIniSettingsDirty(moving_window); g.NavDisableMouseHover = true; @@ -10203,7 +10351,7 @@ static void ImGui::NavUpdateWindowing() } // Apply final focus - if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowDockStop)) + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) { ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL; ClearActiveID(); @@ -10215,8 +10363,14 @@ static void ImGui::NavUpdateWindowing() if (apply_focus_window->NavLastIds[0] == 0) NavInitWindow(apply_focus_window, false); - // If the window only has a menu layer, select it directly - if (apply_focus_window->DC.NavLayerActiveMask == (1 << ImGuiNavLayer_Menu)) + // If the window has ONLY a menu layer (no main layer), select it directly + // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame, + // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since + // the target window as already been previewed once. + // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases, + // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask* + // won't be valid. + if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) g.NavLayer = ImGuiNavLayer_Menu; // Request OS level focus @@ -10229,10 +10383,12 @@ static void ImGui::NavUpdateWindowing() // Apply menu/layer toggle if (apply_toggle_layer && g.NavWindow) { + ClearActiveID(); + // Move to parent menu if necessary ImGuiWindow* new_nav_window = g.NavWindow; while (new_nav_window->ParentWindow - && (new_nav_window->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) == 0 + && (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) new_nav_window = new_nav_window->ParentWindow; @@ -10245,11 +10401,11 @@ static void ImGui::NavUpdateWindowing() g.NavDisableHighlight = false; g.NavDisableMouseHover = true; - // When entering a regular menu bar with the Alt key, we always reinitialize the navigation ID. It however persist on docking tab tabs. - const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main; + // Reinitialize navigation when entering menu bar with the Alt key. + const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main; const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL); if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id) - g.NavWindow->NavLastIds[ImGuiNavLayer_Menu] = 0; + g.NavWindow->NavLastIds[new_nav_layer] = 0; NavRestoreLayer(new_nav_layer); } } @@ -10285,6 +10441,7 @@ void ImGui::NavUpdateWindowingOverlay() for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) { ImGuiWindow* window = g.WindowsFocusOrder[n]; + IM_ASSERT(window != NULL); // Fix static analyzers if (!IsWindowNavFocusable(window)) continue; const char* label = window->Name; @@ -10315,27 +10472,45 @@ void ImGui::ClearDragDrop() memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); } -// Call when current ID is active. // When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +// If the item has an identifier: +// - This assume/require the item to be activated (typically via ButtonBehavior). +// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to activate with another button. +// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag. +// If the item has no identifier: +// - Currently always assume left mouse button. bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button, + // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic). + ImGuiMouseButton mouse_button = ImGuiMouseButton_Left; + bool source_drag_active = false; ImGuiID source_id = 0; ImGuiID source_parent_id = 0; - ImGuiMouseButton mouse_button = ImGuiMouseButton_Left; if (!(flags & ImGuiDragDropFlags_SourceExtern)) { - source_id = window->DC.LastItemId; - if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case - return false; - if (g.IO.MouseDown[mouse_button] == false) - return false; - - if (source_id == 0) + source_id = g.LastItemData.ID; + if (source_id != 0) + { + // Common path: items with ID + if (g.ActiveId != source_id) + return false; + if (g.ActiveIdMouseButton != -1) + mouse_button = g.ActiveIdMouseButton; + if (g.IO.MouseDown[mouse_button] == false) + return false; + g.ActiveIdAllowOverlap = false; + } + else { + // Uncommon path: items without ID + if (g.IO.MouseDown[mouse_button] == false) + return false; + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) @@ -10345,15 +10520,16 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } // Early out - if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) return false; // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() // We build a throwaway ID based on current ID stack + relative AABB of items in window. // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. - source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); - bool is_hovered = ItemHoverable(window->DC.LastItemRect, source_id); + // Rely on keeping other window->LastItemXXX fields intact. + source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); + bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id); if (is_hovered && g.IO.MouseClicked[mouse_button]) { SetActiveID(source_id, window); @@ -10362,19 +10538,13 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. g.ActiveIdAllowOverlap = is_hovered; } - else - { - g.ActiveIdAllowOverlap = false; - } if (g.ActiveId != source_id) return false; source_parent_id = window->IDStack.back(); source_drag_active = IsMouseDragging(mouse_button); - // Disable navigation and key inputs while dragging - g.ActiveIdUsingNavDirMask = ~(ImU32)0; - g.ActiveIdUsingNavInputMask = ~(ImU32)0; - g.ActiveIdUsingKeyInputMask = ~(ImU64)0; + // Disable navigation and key inputs while dragging + cancel existing request if any + SetActiveIdUsingNavAndKeys(); } else { @@ -10415,7 +10585,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) - window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; return true; } @@ -10489,7 +10659,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; - if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow) + if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) return false; IM_ASSERT(id != 0); if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) @@ -10515,14 +10685,14 @@ bool ImGui::BeginDragDropTarget() return false; ImGuiWindow* window = g.CurrentWindow; - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)) return false; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; - if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow) + if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) return false; - const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; - ImGuiID id = window->DC.LastItemId; + const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect; + ImGuiID id = g.LastItemData.ID; if (id == 0) id = window->GetIDFromRectangle(display_rect); if (g.DragDropPayload.SourceId == id) @@ -10564,17 +10734,11 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop } // Render default drop visuals + // FIXME-DRAGDROP: Settle on a proper default visuals for drop target. payload.Preview = was_accepted_previously; flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - { - // FIXME-DRAG: Settle on a proper default visuals for drop target. - r.Expand(3.5f); - bool push_clip_rect = !window->ClipRect.Contains(r); - if (push_clip_rect) window->DrawList->PushClipRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1)); - window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); - if (push_clip_rect) window->DrawList->PopClipRect(); - } + window->DrawList->AddRect(r.Min - ImVec2(3.5f,3.5f), r.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); g.DragDropAcceptFrameCount = g.FrameCount; payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() @@ -10607,14 +10771,8 @@ void ImGui::EndDragDropTarget() //----------------------------------------------------------------------------- // Pass text data straight to log (without being displayed) -void ImGui::LogText(const char* fmt, ...) +static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args) { - ImGuiContext& g = *GImGui; - if (!g.LogEnabled) - return; - - va_list args; - va_start(args, fmt); if (g.LogFile) { g.LogBuffer.Buf.resize(0); @@ -10625,9 +10783,29 @@ void ImGui::LogText(const char* fmt, ...) { g.LogBuffer.appendfv(fmt, args); } +} + +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + LogTextV(g, fmt, args); va_end(args); } +void ImGui::LogTextV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogTextV(g, fmt, args); +} + // Internal version that takes a position to decide on newline placement and pad items according to their depth. // We split text into individual lines to add current tree level padding // FIXME: This code is a little complicated perhaps, considering simplifying the whole system. @@ -11157,7 +11335,6 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl buf->appendf("Collapsed=%d\n", settings->Collapsed); if (settings->DockId != 0) { - // Write DockId as 4 digits if possible. Automatic DockId are small numbers, but full explicit DockSpace() are full ImGuiID range. if (settings->DockOrder == -1) buf->appendf("DockId=0x%08X\n", settings->DockId); else @@ -11187,7 +11364,8 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl // - UpdateViewportsNewFrame() [Internal] // - UpdateViewportsEndFrame() [Internal] // - AddUpdateViewport() [Internal] -// - UpdateSelectWindowViewport() [Internal] +// - WindowSelectViewport() [Internal] +// - WindowSyncOwnedViewport() [Internal] // - UpdatePlatformWindows() // - RenderPlatformWindowsDefault() // - FindPlatformMonitorForPos() [Internal] @@ -11203,6 +11381,7 @@ ImGuiViewport* ImGui::GetMainViewport() return g.Viewports[0]; } +// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236) ImGuiViewport* ImGui::FindViewportByID(ImGuiID id) { ImGuiContext& g = *GImGui; @@ -11445,10 +11624,10 @@ static void ImGui::UpdateViewportsNewFrame() // Update/copy monitor info UpdateViewportPlatformMonitor(viewport); - // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again. - viewport->WorkOffsetMin = viewport->CurrWorkOffsetMin; - viewport->WorkOffsetMax = viewport->CurrWorkOffsetMax; - viewport->CurrWorkOffsetMin = viewport->CurrWorkOffsetMax = ImVec2(0.0f, 0.0f); + // Lock down space taken by menu bars and status bars, reset the offset for functions like BeginMainMenuBar() to alter them again. + viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin; + viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax; + viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f); viewport->UpdateWorkRect(); // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back. @@ -11485,6 +11664,17 @@ static void ImGui::UpdateViewportsNewFrame() viewport->DpiScale = new_dpi_scale; } + // Update fallback monitor + if (g.PlatformIO.Monitors.Size == 0) + { + ImGuiPlatformMonitor* monitor = &g.FallbackMonitor; + monitor->MainPos = main_viewport->Pos; + monitor->MainSize = main_viewport->Size; + monitor->WorkPos = main_viewport->WorkPos; + monitor->WorkSize = main_viewport->WorkSize; + monitor->DpiScale = main_viewport->DpiScale; + } + if (!viewports_enabled) { g.MouseViewport = main_viewport; @@ -11527,6 +11717,7 @@ static void ImGui::UpdateViewportsNewFrame() // - when releasing a moving window we will revert to aiming behind (at viewport_hovered) // - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't have tooltips in their viewports (when lacking monitor info) // - consider the case of holding on a menu item to browse child menus: even thou a mouse button is held, there's no active id because menu items only react on mouse release. + // FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to trust when viewport_hovered==NULL and use that. const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive; if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL) viewport_hovered = g.MouseLastHoveredViewport; @@ -11567,7 +11758,7 @@ ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const flags |= ImGuiViewportFlags_IsPlatformWindow; if (window != NULL) { - if (g.MovingWindow && g.MovingWindow->RootWindow == window) + if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window) flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing; if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs)) flags |= ImGuiViewportFlags_NoInputs; @@ -11622,7 +11813,7 @@ ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const } // FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. -static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) +static void ImGui::WindowSelectViewport(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; @@ -11647,7 +11838,7 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0) { // By default inherit from parent window - if (window->Viewport == NULL && window->ParentWindow && !window->ParentWindow->IsFallbackWindow) + if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive)) window->Viewport = window->ParentWindow->Viewport; // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file @@ -11680,7 +11871,7 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) { window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); } - else if (g.MovingWindow && g.MovingWindow->RootWindow == window && IsMousePosValid()) + else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid()) { if (window->Viewport != NULL && window->Viewport->Window == window) window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); @@ -11698,7 +11889,7 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) if (window->Viewport == NULL) if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport)) window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); - + // Mark window as allowed to protrude outside of its viewport and into the current monitor if (!lock_viewport) { @@ -11749,6 +11940,92 @@ static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) // window->Flags |= ImGuiWindowFlags_NoTitleBar; } +void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack) +{ + ImGuiContext& g = *GImGui; + + bool viewport_rect_changed = false; + + // Synchronize window --> viewport in most situations + // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM + if (window->Viewport->PlatformRequestMove) + { + window->Pos = window->Viewport->Pos; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Pos = window->Pos; + } + + if (window->Viewport->PlatformRequestResize) + { + window->Size = window->SizeFull = window->Viewport->Size; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Size = window->Size; + } + window->Viewport->UpdateWorkRect(); + + // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() + // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. + if (viewport_rect_changed) + UpdateViewportPlatformMonitor(window->Viewport); + + // Update common viewport flags + const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; + ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; + ImGuiWindowFlags window_flags = window->Flags; + const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0; + const bool is_short_lived_floating_window = (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; + if (window_flags & ImGuiWindowFlags_Tooltip) + viewport_flags |= ImGuiViewportFlags_TopMost; + if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; + if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) + viewport_flags |= ImGuiViewportFlags_NoDecoration; + + // Not correct to set modal as topmost because: + // - Because other popups can be stacked above a modal (e.g. combo box in a modal) + // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" + //if (flags & ImGuiWindowFlags_Modal) + // viewport_flags |= ImGuiViewportFlags_TopMost; + + // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them + // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). + // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, + // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. + if (is_short_lived_floating_window && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; + + // We can overwrite viewport flags using ImGuiWindowClass (advanced users) + if (window->WindowClass.ViewportFlagsOverrideSet) + viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; + if (window->WindowClass.ViewportFlagsOverrideClear) + viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; + + // We can also tell the backend that clearing the platform window won't be necessary, + // as our window background is filling the viewport and we have disabled BgAlpha. + // FIXME: Work on support for per-viewport transparency (#2766) + if (!(window_flags & ImGuiWindowFlags_NoBackground)) + viewport_flags |= ImGuiViewportFlags_NoRendererClear; + + window->Viewport->Flags = viewport_flags; + + // Update parent viewport ID + // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) + if (window->WindowClass.ParentViewportId) + window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId; + else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack && (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive)) + window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID; + else + window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; +} + // Called by user at the end of the main loop, after EndFrame() // This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api. void ImGui::UpdatePlatformWindows() @@ -11961,6 +12238,17 @@ static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport) viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect()); } +// Return value is always != NULL, but don't hold on it across frames. +const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p) +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)viewport_p; + int monitor_idx = viewport->PlatformMonitor; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + return &g.PlatformIO.Monitors[monitor_idx]; + return &g.FallbackMonitor; +} + void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; @@ -12120,14 +12408,14 @@ struct ImGuiDockNodeSettings ImGuiID ID; ImGuiID ParentNodeId; ImGuiID ParentWindowId; - ImGuiID SelectedWindowId; + ImGuiID SelectedTabId; signed char SplitAxis; char Depth; ImGuiDockNodeFlags Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_) ImVec2ih Pos; ImVec2ih Size; ImVec2ih SizeRef; - ImGuiDockNodeSettings() { ID = ParentNodeId = ParentWindowId = SelectedWindowId = 0; SplitAxis = ImGuiAxis_None; Depth = 0; Flags = ImGuiDockNodeFlags_None; } + ImGuiDockNodeSettings() { memset(this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; } }; //----------------------------------------------------------------------------- @@ -12169,7 +12457,7 @@ namespace ImGui static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window); static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking); static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data); - static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos); + static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos); static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired); static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos); static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; } @@ -12327,8 +12615,8 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) { if (hovered_window->DockNodeAsHost) g.HoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos); - else if (hovered_window->RootWindowDockStop->DockNode) - g.HoveredDockNode = hovered_window->RootWindowDockStop->DockNode; + else if (hovered_window->RootWindow->DockNode) + g.HoveredDockNode = hovered_window->RootWindow->DockNode; } // Process Docking requests @@ -12353,7 +12641,8 @@ static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx) { // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used) - // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry. We should poke in ctx->Nodes to find a suitable ID faster. + // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0 + // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already sorted. ImGuiID id = 0x0001; while (DockContextFindNodeByID(ctx, id) != NULL) id++; @@ -12504,9 +12793,9 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDoc node->ParentNode->ChildNodes[0] = node; else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL) node->ParentNode->ChildNodes[1] = node; - node->SelectedTabId = settings->SelectedWindowId; + node->SelectedTabId = settings->SelectedTabId; node->SplitAxis = (ImGuiAxis)settings->SplitAxis; - node->LocalFlags |= (settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); + node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); // Bind host window immediately if it already exist (in case of a rebuild) // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set. @@ -12639,7 +12928,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) ImGuiDir split_dir = req->DockSplitDir; if (split_dir != ImGuiDir_None) { - // Split into one, one side will be our payload node unless we are dropping a loose window + // Split into two, one side will be our payload node unless we are dropping a loose window const ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; const int split_inheritor_child_idx = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0; // Current contents will be moved to the opposite side const float split_ratio = req->DockSplitRatio; @@ -12648,7 +12937,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) new_node->HostWindow = node->HostWindow; node = new_node; } - node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); if (node != payload_node) { @@ -12686,8 +12975,8 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) IM_ASSERT(last_focused_node != NULL); ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node); IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node)); - last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; - node->LocalFlags &= ~ImGuiDockNodeFlags_CentralNode; + last_focused_node->SetLocalFlags(last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode); + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode); last_focused_root_node->CentralNode = last_focused_node; } @@ -12724,6 +13013,29 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) MarkIniSettingsDirty(); } +// Problem: +// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more +// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly problematic +// with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well (the later can be +// due to missing ImGuiBackendFlags_HasMouseCursors backend flag). +// Solution: +// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor. +// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch). +static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* ref_viewport) +{ + if (ref_viewport == NULL) + return size; + + ImGuiContext& g = *GImGui; + ImVec2 max_size = ImFloor(ref_viewport->WorkSize * 0.90f); + if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) + { + const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ref_viewport); + max_size = ImFloor(monitor->WorkSize * 0.90f); + } + return ImMin(size, max_size); +} + void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref) { IMGUI_DEBUG_LOG_DOCKING("DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref); @@ -12734,7 +13046,9 @@ void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* windo window->DockId = 0; window->Collapsed = false; window->DockIsActive = false; - window->DockTabIsVisible = false; + window->DockNodeIsVisible = window->DockTabIsVisible = false; + window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport); + MarkIniSettingsDirty(); } @@ -12748,6 +13062,9 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) { // In the case of a root node or central node, the node will have to stay in place. Create a new node to receive the payload. ImGuiDockNode* new_node = DockContextAddNode(ctx, 0); + new_node->Pos = node->Pos; + new_node->Size = node->Size; + new_node->SizeRef = node->SizeRef; DockNodeMoveWindows(new_node, node); DockSettingsRenameNodeReferences(node->ID, new_node->ID); for (int n = 0; n < new_node->Windows.Size; n++) @@ -12756,7 +13073,7 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) } else { - // Otherwise extract our node and merging our sibling back into the parent node. + // Otherwise extract our node and merge our sibling back into the parent node. IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node); int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1; node->ParentNode->ChildNodes[index_in_parent] = NULL; @@ -12764,7 +13081,8 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) node->ParentNode->AuthorityForViewport = ImGuiDataAuthority_Window; // The node that stays in place keeps the viewport, so our newly dragged out node will create a new viewport node->ParentNode = NULL; } - node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_Window; + node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode; + node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport); node->WantMouseMove = true; MarkIniSettingsDirty(); } @@ -12772,20 +13090,16 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) // This is mostly used for automation. bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos) { - if (split_outer) - { - IM_ASSERT(0); - } - else - { - ImGuiDockPreviewData split_data; - DockNodePreviewDockSetup(target, target_node, payload, &split_data, false, split_outer); - if (split_data.DropRectsDraw[split_dir+1].IsInverted()) - return false; - *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter(); - return true; - } - return false; + // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects + // (which would be functionally identical) we only show the outer one. Reflect this here. + if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None) + split_outer = true; + ImGuiDockPreviewData split_data; + DockNodePreviewDockSetup(target, target_node, payload, &split_data, false, split_outer); + if (split_data.DropRectsDraw[split_dir+1].IsInverted()) + return false; + *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter(); + return true; } //----------------------------------------------------------------------------- @@ -12823,7 +13137,7 @@ bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* ImGuiDockNode::ImGuiDockNode(ImGuiID id) { ID = id; - SharedFlags = LocalFlags = ImGuiDockNodeFlags_None; + SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None; ParentNode = ChildNodes[0] = ChildNodes[1] = NULL; TabBar = NULL; SplitAxis = ImGuiAxis_None; @@ -12838,7 +13152,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id) AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode; AuthorityForViewport = ImGuiDataAuthority_Auto; IsVisible = true; - IsFocused = HasCloseButton = HasWindowMenuButton = EnableCloseButton = false; + IsFocused = HasCloseButton = HasWindowMenuButton = false; WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false; MarkedForPosSizeWrite = false; } @@ -12859,6 +13173,12 @@ int ImGui::DockNodeGetTabOrder(ImGuiWindow* window) return tab ? tab_bar->GetTabOrder(tab) : -1; } +static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window) +{ + window->Hidden = true; + window->HiddenFramesCanSkipItems = window->Active ? 1 : 2; +} + static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar) { ImGuiContext& g = *GImGui; (void)g; @@ -12871,6 +13191,12 @@ static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, b IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); IMGUI_DEBUG_LOG_DOCKING("DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name); + // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window, + // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not visible for one frame). + // We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin() + if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false) + DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]); + node->Windows.push_back(window); node->WantHiddenTabBarUpdate = true; window->DockNode = node; @@ -12878,14 +13204,6 @@ static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, b window->DockIsActive = (node->Windows.Size > 1); window->DockTabWantClose = false; - // If more than 2 windows appeared on the same frame, we'll create a new hosting DockNode from the point of the second window submission. - // Then we need to hide the first window (after its been output) otherwise it would be visible as a standalone window for one frame. - if (node->HostWindow == NULL && node->Windows.Size == 2 && node->Windows[0]->WasActive == false) - { - node->Windows[0]->Hidden = true; - node->Windows[0]->HiddenFramesCanSkipItems = 1; - } - // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage. // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one. if (node->HostWindow == NULL && node->IsFloatingNode()) @@ -12924,7 +13242,7 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window { ImGuiContext& g = *GImGui; IM_ASSERT(window->DockNode == node); - //IM_ASSERT(window->RootWindow == node->HostWindow); + //IM_ASSERT(window->RootWindowDockTree == node->HostWindow); //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin() IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID); IMGUI_DEBUG_LOG_DOCKING("DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name); @@ -13123,6 +13441,7 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod // Remove inactive windows // Merge node flags overrides stored in windows + node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; for (int window_n = 0; window_n < node->Windows.Size; window_n++) { ImGuiWindow* window = node->Windows[window_n]; @@ -13148,13 +13467,15 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod } else { - node->LocalFlags &= ~window->WindowClass.DockNodeFlagsOverrideClear; - node->LocalFlags |= window->WindowClass.DockNodeFlagsOverrideSet; + // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this. + //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear; + node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet; } } + node->UpdateMergedFlags(); // Auto-hide tab bar option - ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + ImGuiDockNodeFlags node_flags = node->MergedFlags; if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar()) node->WantHiddenTabBarToggle = true; node->WantHiddenTabBarUpdate = false; @@ -13165,9 +13486,9 @@ static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* nod // Apply toggles at a single point of the frame (here!) if (node->Windows.Size > 1) - node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); else if (node->WantHiddenTabBarToggle) - node->LocalFlags ^= ImGuiDockNodeFlags_HiddenTabBar; + node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); node->WantHiddenTabBarToggle = false; DockNodeUpdateVisibleFlag(node); @@ -13223,6 +13544,20 @@ static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) } } +static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window) +{ + // Remove ourselves from any previous different host window + // This can happen if a user mistakenly does (see #4295 for details): + // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace + // - N+1: NewFrame() // will create floating host window for that node + // - N+1: DockSpace(id) // requalify node as dockspace, moving host window + if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node) + node->HostWindow->DockNodeAsHost = NULL; + + host_window->DockNodeAsHost = node; + node->HostWindow = host_window; +} + static void ImGui::DockNodeUpdate(ImGuiDockNode* node) { ImGuiContext& g = *GImGui; @@ -13272,7 +13607,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow; node->WantCloseAll = false; node->WantCloseTabId = 0; - node->HasCloseButton = node->HasWindowMenuButton = node->EnableCloseButton = false; + node->HasCloseButton = node->HasWindowMenuButton = false; node->LastFrameActive = g.FrameCount; if (node->WantMouseMove && node->Windows.Size == 1) @@ -13305,7 +13640,20 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) } } - const ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + const ImGuiDockNodeFlags node_flags = node->MergedFlags; + + // Decide if the node will have a close button and a window menu button + node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; + node->HasCloseButton = false; + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call. + ImGuiWindow* window = node->Windows[window_n]; + node->HasCloseButton |= window->HasCloseButton; + window->DockIsActive = (node->Windows.Size > 1); + } + if (node_flags & ImGuiDockNodeFlags_NoCloseButton) + node->HasCloseButton = false; // Bind or create host window ImGuiWindow* host_window = NULL; @@ -13314,25 +13662,11 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) { // [Explicit root dockspace node] IM_ASSERT(node->HostWindow); - node->EnableCloseButton = false; - node->HasCloseButton = (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0; - node->HasWindowMenuButton = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; host_window = node->HostWindow; } else { // [Automatic root or child nodes] - node->EnableCloseButton = false; - node->HasCloseButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0; - node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; - for (int window_n = 0; window_n < node->Windows.Size; window_n++) - { - // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call. - ImGuiWindow* window = node->Windows[window_n]; - window->DockIsActive = (node->Windows.Size > 1); - node->EnableCloseButton |= window->HasCloseButton; - } - if (node->IsRootNode() && node->IsVisible) { ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL; @@ -13372,8 +13706,8 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) PopStyleVar(); beginned_into_host_window = true; - node->HostWindow = host_window = g.CurrentWindow; - host_window->DockNodeAsHost = node; + host_window = g.CurrentWindow; + DockNodeSetupHostWindow(node, host_window); host_window->DC.CursorPos = host_window->Pos; node->Pos = host_window->Pos; node->Size = host_window->Size; @@ -13402,8 +13736,8 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) if (node->IsSplitNode()) IM_ASSERT(node->TabBar == NULL); if (node->IsRootNode()) - if (g.NavWindow && g.NavWindow->RootWindowDockStop->DockNode && g.NavWindow->RootWindowDockStop->ParentWindow == host_window) - node->LastFocusedNodeId = g.NavWindow->RootWindowDockStop->DockNode->ID; + if (g.NavWindow && g.NavWindow->RootWindow->DockNode && g.NavWindow->RootWindow->ParentWindow == host_window) + node->LastFocusedNodeId = g.NavWindow->RootWindow->DockNode->ID; // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size // _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order! @@ -13431,10 +13765,10 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) ImGuiDockNode* root_node = DockNodeGetRootNode(central_node); ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size); ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size); - if (hole_rect.Min.x > root_rect.Min.x) { hole_rect.Min.x += WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS; } - if (hole_rect.Max.x < root_rect.Max.x) { hole_rect.Max.x -= WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS; } - if (hole_rect.Min.y > root_rect.Min.y) { hole_rect.Min.y += WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS; } - if (hole_rect.Max.y < root_rect.Max.y) { hole_rect.Max.y -= WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS; } + if (hole_rect.Min.x > root_rect.Min.x) { hole_rect.Min.x += WINDOWS_HOVER_PADDING; } + if (hole_rect.Max.x < root_rect.Max.x) { hole_rect.Max.x -= WINDOWS_HOVER_PADDING; } + if (hole_rect.Min.y > root_rect.Min.y) { hole_rect.Min.y += WINDOWS_HOVER_PADDING; } + if (hole_rect.Max.y < root_rect.Max.y) { hole_rect.Max.y -= WINDOWS_HOVER_PADDING; } //GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255)); if (central_node_hole && !hole_rect.IsInverted()) { @@ -13483,7 +13817,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) // Draw payload drop target if (host_window && node->IsVisible) - if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindow != host_window)) + if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window)) BeginDockableDragDropTarget(host_window); // We update this after DockNodeUpdateTabBar() @@ -13558,13 +13892,13 @@ bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node) { if (node->TabBar == NULL || node->HostWindow == NULL) return false; - if (node->SharedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) return false; Begin(node->HostWindow->Name); PushOverrideID(node->ID); bool ret = BeginTabBarEx(node->TabBar, node->TabBar->BarRect, node->TabBar->Flags, node); IM_UNUSED(ret); - IM_ASSERT(ret); + IM_ASSERT(ret); return true; } @@ -13592,7 +13926,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w ImGuiDockNode* root_node = DockNodeGetRootNode(node); if (g.NavWindowingTarget) is_focused = (g.NavWindowingTarget->DockNode == node); - else if (g.NavWindow && g.NavWindow->RootWindowForTitleBarHighlight == host_window->RootWindow && root_node->LastFocusedNodeId == node->ID) + else if (g.NavWindow && g.NavWindow->RootWindowForTitleBarHighlight == host_window->RootWindowDockTree && root_node->LastFocusedNodeId == node->ID) is_focused = true; // Hidden tab bar will show a triangle on the upper-left (in Begin) @@ -13636,9 +13970,8 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w ImGuiID focus_tab_id = 0; node->IsFocused = is_focused; - const ImGuiDockNodeFlags node_flags = node->GetMergedFlags(); + const ImGuiDockNodeFlags node_flags = node->MergedFlags; const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None); - const bool has_close_button = (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0; // In a dock node, the Collapse Button turns into the Window Menu button. // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes? @@ -13652,15 +13985,14 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w // Layout ImRect title_bar_rect, tab_bar_rect; ImVec2 window_menu_button_pos; - DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos); + ImVec2 close_button_pos; + DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos); - // Submit new tabs and apply NavWindow focus back to the tab bar. They will be added as Unsorted and sorted below based on relative DockOrder value. + // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value. const int tabs_count_old = tab_bar->Tabs.Size; for (int window_n = 0; window_n < node->Windows.Size; window_n++) { ImGuiWindow* window = node->Windows[window_n]; - if (g.NavWindow && g.NavWindow->RootWindowDockStop == window) - tab_bar->SelectedTabId = window->ID; if (TabBarFindTabByID(tab_bar, window->ID) == NULL) TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window); } @@ -13669,12 +14001,12 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w if (is_focused) node->LastFrameFocused = g.FrameCount; ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, ImDrawCornerFlags_Top); + host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, ImDrawFlags_RoundCornersTop); // Docking/Collapse button if (has_window_menu_button) { - if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos, node)) + if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos, node)) // == DockNodeGetWindowMenuButtonId(node) OpenPopup("#WindowMenu"); if (IsItemActive()) focus_tab_id = tab_bar->SelectedTabId; @@ -13697,6 +14029,10 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start, sizeof(ImGuiTabItem), TabItemComparerByDockOrder); } + // Apply NavWindow focus back to the tab bar + if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node) + tab_bar->SelectedTabId = g.NavWindow->RootWindow->ID; + // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL) tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId; @@ -13744,11 +14080,11 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w node->VisibleWindow = window; // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call - window->DockTabItemStatusFlags = host_window->DC.LastItemStatusFlags; - window->DockTabItemRect = host_window->DC.LastItemRect; + window->DockTabItemStatusFlags = g.LastItemData.StatusFlags; + window->DockTabItemRect = g.LastItemData.Rect; // Update navigation ID on menu layer - if (g.NavWindow && g.NavWindow->RootWindowDockStop == window && (window->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) + if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) host_window->NavLastIds[1] = window->ID; } } @@ -13764,23 +14100,25 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w // Close button (after VisibleWindow was updated) // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->ID may be != from tab_bar->SelectedTabId - if (has_close_button && node->VisibleWindow) + const bool close_button_is_enabled = node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton; + const bool close_button_is_visible = node->HasCloseButton; + //const bool close_button_is_visible = close_button_is_enabled; // Most people would expect this behavior of not even showing the button (leaving a hole since we can't claim that space as other windows in the tba bar have one) + if (close_button_is_visible) { - if (!node->VisibleWindow->HasCloseButton) + if (!close_button_is_enabled) { PushItemFlag(ImGuiItemFlags_Disabled, true); - PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.5f)); + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.4f)); + } + if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos)) + { + node->WantCloseAll = true; + for (int n = 0; n < tab_bar->Tabs.Size; n++) + TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]); } - const float button_sz = g.FontSize; - if (CloseButton(host_window->GetID("#CLOSE"), title_bar_rect.GetTR() + ImVec2(-style.FramePadding.x * 2.0f - button_sz, 0.0f))) - if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->VisibleTabId)) - { - node->WantCloseTabId = tab->ID; - TabBarCloseTab(tab_bar, tab); - } //if (IsItemActive()) // focus_tab_id = tab_bar->SelectedTabId; - if (!node->VisibleWindow->HasCloseButton) + if (!close_button_is_enabled) { PopStyleColor(); PopItemFlag(); @@ -13798,7 +14136,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w { // ImGuiButtonFlags_AllowItemOverlap + SetItemAllowOverlap() required for appending into dock node tab bar, // otherwise dragging window will steal HoveredId and amended tabs cannot get them. - host_window->DC.LastItemId = title_bar_id; + g.LastItemData.ID = title_bar_id; SetItemAllowOverlap(); } if (held) @@ -13890,27 +14228,35 @@ static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* } // window menu button == collapse button when not in a dock node. -// FIXME: This is similar to RenderWindowTitleBarContents, may want to share code. -static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos) +// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code. +static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos) { ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f); if (out_title_rect) { *out_title_rect = r; } + r.Min.x += style.WindowBorderSize; + r.Max.x -= style.WindowBorderSize; + + float button_sz = g.FontSize; + ImVec2 window_menu_button_pos = r.Min; - r.Min.x += g.Style.FramePadding.x; - r.Max.x -= g.Style.FramePadding.x; + r.Min.x += style.FramePadding.x; + r.Max.x -= style.FramePadding.x; if (node->HasCloseButton) { - r.Max.x -= g.FontSize;// +1.0f; // In DockNodeUpdateTabBar() we currently display a disabled close button even if there is none. + r.Max.x -= button_sz; + if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - style.FramePadding.x, r.Min.y); } - if (node->HasWindowMenuButton && g.Style.WindowMenuButtonPosition == ImGuiDir_Left) + if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left) { - r.Min.x += g.FontSize; // + g.Style.ItemInnerSpacing.x; // <-- Adding ItemInnerSpacing makes the title text moves slightly when in a docking tab bar. Instead we adjusted RenderArrowDockMenu() + r.Min.x += button_sz + style.ItemInnerSpacing.x; } - else if (node->HasWindowMenuButton && g.Style.WindowMenuButtonPosition == ImGuiDir_Right) + else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right) { - r.Max.x -= g.FontSize + g.Style.FramePadding.x; + r.Max.x -= button_sz + style.FramePadding.x; window_menu_button_pos = ImVec2(r.Max.x, r.Min.y); } if (out_tab_bar_rect) { *out_tab_bar_rect = r; } @@ -14018,8 +14364,8 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN IM_ASSERT(ref_node_for_rect->IsVisible); // Filter, figure out where we are allowed to dock - ImGuiDockNodeFlags src_node_flags = root_payload_as_host ? root_payload_as_host->GetMergedFlags() : root_payload->WindowClass.DockNodeFlagsOverrideSet; - ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->GetMergedFlags() : host_window->WindowClass.DockNodeFlagsOverrideSet; + ImGuiDockNodeFlags src_node_flags = root_payload_as_host ? root_payload_as_host->MergedFlags : root_payload->WindowClass.DockNodeFlagsOverrideSet; + ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet; data->IsCenterAvailable = true; if (is_outer_docking) data->IsCenterAvailable = false; @@ -14029,7 +14375,11 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->IsCenterAvailable = false; else if ((!host_node || !host_node->IsEmpty()) && root_payload_as_host && root_payload_as_host->IsSplitNode() && (root_payload_as_host->OnlyNodeWithWindows == NULL)) // Is _visibly_ split? data->IsCenterAvailable = false; - else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) || (src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther)) + else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty())) + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty()) data->IsCenterAvailable = false; data->IsSidesAvailable = true; @@ -14065,9 +14415,9 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN } } - // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar + // We only allow and preview docking when hovering over a drop rect or over the title bar data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable); - if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift) + if (!is_explicit_target && !data->IsSplitDirExplicit) data->IsDropAllowed = false; // Calculate split area @@ -14127,7 +14477,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock { // Compute target tab bar geometry so we can locate our preview tabs ImRect tab_bar_rect; - DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL); + DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL, NULL); ImVec2 tab_pos = tab_bar_rect.Min; if (host_node && host_node->TabBar) { @@ -14199,7 +14549,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock } // Stop after ImGuiDir_None - if ((host_node && (host_node->GetMergedFlags() & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit) + if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit) return; } } @@ -14252,6 +14602,9 @@ void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; + child_0->UpdateMergedFlags(); + child_1->UpdateMergedFlags(); + parent_node->UpdateMergedFlags(); if (child_inheritor->IsCentralNode()) DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor; } @@ -14291,6 +14644,8 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; + parent_node->LocalFlagsInWindows = (child_0 ? child_0->LocalFlagsInWindows : 0) | (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows + parent_node->UpdateMergedFlags(); if (child_0) { @@ -14422,7 +14777,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) bb.Max[axis ^ 1] += child_1->Size[axis ^ 1]; //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255)); - const ImGuiDockNodeFlags merged_flags = child_0->GetMergedFlags() | child_1->GetMergedFlags(); + const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY; if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag)) { @@ -14469,7 +14824,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) float cur_size_1 = child_1->Size[axis]; float min_size_0 = resize_limits[0] - child_0->Pos[axis]; float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1]; - if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER)) + if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER)) { if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0) { @@ -14595,13 +14950,13 @@ void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) // Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default. // The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors. // DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app. -void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) +ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) { ImGuiContext* ctx = GImGui; ImGuiContext& g = *ctx; ImGuiWindow* window = GetCurrentWindow(); if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) - return; + return 0; // Early out if parent window is hidden/collapsed // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960. @@ -14616,7 +14971,7 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags fla { IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X created\n", id); node = DockContextAddNode(ctx, id); - node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode); } if (window_class && window_class->ClassId != node->WindowClass.ClassId) IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id, node->WindowClass.ClassId, window_class->ClassId); @@ -14628,16 +14983,16 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags fla if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly)) { IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID"); - node->LocalFlags |= ImGuiDockNodeFlags_DockSpace; - return; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); + return id; } - node->LocalFlags |= ImGuiDockNodeFlags_DockSpace; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible if (flags & ImGuiDockNodeFlags_KeepAliveOnly) { node->LastFrameAlive = g.FrameCount; - return; + return id; } const ImVec2 content_avail = GetContentRegionAvail(); @@ -14654,27 +15009,23 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags fla SetNextWindowSize(node->Size); g.NextWindowData.PosUndock = false; - // FIXME-DOCK Why do we need a child window to host a dockspace, could we host it in the existing window? + // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window? + // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented) ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost; window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + window_flags |= ImGuiWindowFlags_NoBackground; char title[256]; ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, id); - // FIXME-DOCK: What is the reason for not simply calling BeginChild()? - if (node->Windows.Size > 0 || node->IsSplitNode()) - PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); Begin(title, NULL, window_flags); PopStyleVar(); - if (node->Windows.Size > 0 || node->IsSplitNode()) - PopStyleColor(); ImGuiWindow* host_window = g.CurrentWindow; - host_window->DockNodeAsHost = node; + DockNodeSetupHostWindow(node, host_window); host_window->ChildId = window->GetID(title); - node->HostWindow = host_window; node->OnlyNodeWithWindows = NULL; IM_ASSERT(node->IsRootNode()); @@ -14686,13 +15037,14 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags fla // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property, // as it doesn't make sense for an empty dockspace to not have this property. if (node->IsLeafNode() && !node->IsCentralNode()) - node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode); // Update the node DockNodeUpdate(node); End(); ItemSize(size); + return id; } // Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode! @@ -14811,11 +15163,11 @@ void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags) { ImGuiContext* ctx = GImGui; - ImGuiDockNode* node = NULL; if (id != 0) DockBuilderRemoveNode(id); + ImGuiDockNode* node = NULL; if (flags & ImGuiDockNodeFlags_DockSpace) { DockSpace(id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly); @@ -14824,7 +15176,7 @@ ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags) else { node = DockContextAddNode(ctx, id); - node->LocalFlags = flags; + node->SetLocalFlags(flags); } node->LastFrameAlive = ctx->FrameCount; // Set this otherwise BeginDocked will undock during the same frame. return node->ID; @@ -14838,8 +15190,12 @@ void ImGui::DockBuilderRemoveNode(ImGuiID node_id) return; DockBuilderRemoveNodeDockedWindows(node_id, true); DockBuilderRemoveNodeChildNodes(node_id); + // Node may have moved or deleted if e.g. any merge happened + node = DockContextFindNodeByID(ctx, node_id); + if (node == NULL) + return; if (node->IsCentralNode() && node->ParentNode) - node->ParentNode->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode); DockContextRemoveNode(ctx, node, true); } @@ -14909,8 +15265,8 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) } else if (has_central_node) { - root_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; root_node->CentralNode = root_node; + root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode); } } @@ -14992,10 +15348,12 @@ static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID ds ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx, dst_node_id_if_known); dst_node->SharedFlags = src_node->SharedFlags; dst_node->LocalFlags = src_node->LocalFlags; + dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; dst_node->Pos = src_node->Pos; dst_node->Size = src_node->Size; dst_node->SizeRef = src_node->SizeRef; dst_node->SplitAxis = src_node->SplitAxis; + dst_node->UpdateMergedFlags(); out_node_remap_pairs->push_back(src_node->ID); out_node_remap_pairs->push_back(dst_node->ID); @@ -15018,11 +15376,12 @@ void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVect IM_ASSERT(dst_node_id != 0); IM_ASSERT(out_node_remap_pairs != NULL); + DockBuilderRemoveNode(dst_node_id); + ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, src_node_id); IM_ASSERT(src_node != NULL); out_node_remap_pairs->clear(); - DockBuilderRemoveNode(dst_node_id); DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs); IM_ASSERT((out_node_remap_pairs->Size % 2) == 0); @@ -15209,6 +15568,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) ImGuiContext* ctx = GImGui; ImGuiContext& g = *ctx; + // Clear fields ahead so most early-out paths don't have to do it + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window); if (auto_dock_node) { @@ -15258,14 +15620,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextNewFrameUpdateDocking() ImGuiDockNode* root_node = DockNodeGetRootNode(node); if (root_node->LastFrameAlive < g.FrameCount) - { DockContextProcessUndockWindow(ctx, window); - } else - { window->DockIsActive = true; - window->DockTabIsVisible = false; - } return; } @@ -15278,8 +15635,10 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test) if (node->HostWindow == NULL) { - window->DockIsActive = (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing); - window->DockTabIsVisible = false; + if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing) + window->DockIsActive = true; + if (node->Windows.Size > 1) + DockNodeHideWindowDuringHostWindowCreation(window); return; } @@ -15301,8 +15660,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) SetNextWindowSize(node->Size); g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos() window->DockIsActive = true; + window->DockNodeIsVisible = true; window->DockTabIsVisible = false; - if (node->SharedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) return; // When the window is selected we mark it as visible. @@ -15317,9 +15677,9 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) else window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it: confusingly enough we need a title bar height so we are correctly offset, but it won't be displayed! - // Save new dock order only if the tab bar has been visible once. + // Save new dock order only if the window has been visible once already // This allows multiple windows to be created in the same frame and have their respective dock orders preserved. - if (node->TabBar && node->TabBar->CurrFrameVisible != -1) + if (node->TabBar && window->WasActive) window->DockOrder = (short)DockNodeGetTabOrder(window); if ((node->WantCloseAll || node->WantCloseTabId == window->ID) && p_open != NULL) @@ -15335,11 +15695,12 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) ImGuiContext& g = *GImGui; IM_ASSERT(g.ActiveId == window->MoveId); IM_ASSERT(g.MovingWindow == window); + IM_ASSERT(g.CurrentWindow == window); - window->DC.LastItemId = window->MoveId; - window = window->RootWindow; + g.LastItemData.ID = window->MoveId; + window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); - bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); + bool is_drag_docking = ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit if (is_drag_docking && BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload)) { SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window)); @@ -15356,7 +15717,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) ImGuiContext* ctx = GImGui; ImGuiContext& g = *ctx; - //IM_ASSERT(window->RootWindow == window); // May also be a DockSpace + //IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); if (!g.DragDropActive) return; @@ -15400,7 +15761,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) } const ImRect explicit_target_rect = (node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar()) ? node->TabBar->BarRect : ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight())); - const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max); + const bool is_explicit_target = IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max); // Preview docking request and find out split direction/ratio //const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of delay, but breaks overlapping drop targets within the same window. @@ -15550,7 +15911,7 @@ static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettings if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_HiddenTabBar; } if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton; } if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoCloseButton; } - if (sscanf(line, " Selected=0x%08X%n", &node.SelectedWindowId,&r) == 1) { line += r; } + if (sscanf(line, " Selected=0x%08X%n", &node.SelectedTabId,&r) == 1) { line += r; } if (node.ParentNodeId != 0) if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId)) node.Depth = parent_settings->Depth + 1; @@ -15564,7 +15925,7 @@ static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDo node_settings.ID = node->ID; node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0; node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) ? node->HostWindow->ParentWindow->ID : 0; - node_settings.SelectedWindowId = node->SelectedTabId; + node_settings.SelectedTabId = node->SelectedTabId; node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None); node_settings.Depth = (char)depth; node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_); @@ -15630,8 +15991,8 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings buf->appendf(" NoWindowMenuButton=1"); if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton) buf->appendf(" NoCloseButton=1"); - if (node_settings->SelectedWindowId) - buf->appendf(" Selected=0x%08X", node_settings->SelectedWindowId); + if (node_settings->SelectedTabId) + buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId); #if IMGUI_DEBUG_INI_SETTINGS // [DEBUG] Include comments in the .ini file to ease debugging @@ -15882,6 +16243,10 @@ static void MetricsHelpMarker(const char* desc) } } +#ifndef IMGUI_DISABLE_DEMO_WINDOWS +namespace ImGui { void ShowFontAtlas(ImFontAtlas* atlas); } +#endif + void ImGui::ShowMetricsWindow(bool* p_open) { if (!Begin("Dear ImGui Metrics/Debugger", p_open)) @@ -15984,15 +16349,15 @@ void ImGui::ShowMetricsWindow(bool* p_open) cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); if (cfg->ShowTablesRects && g.NavWindow != NULL) { - for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { - ImGuiTable* table = g.Tables.GetByIndex(table_n); - if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) + ImGuiTable* table = g.Tables.TryGetMapData(table_n); + if (table == NULL || table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) continue; BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); Indent(); char buf[128]; for (int rect_n = 0; rect_n < TRT_Count; rect_n++) @@ -16007,7 +16372,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); } } else @@ -16016,7 +16381,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); } } Unindent(); @@ -16104,22 +16469,36 @@ void ImGui::ShowMetricsWindow(bool* p_open) } // Details for TabBars - if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetSize())) + if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount())) { - for (int n = 0; n < g.TabBars.GetSize(); n++) - DebugNodeTabBar(g.TabBars.GetByIndex(n), "TabBar"); + for (int n = 0; n < g.TabBars.GetMapSize(); n++) + if (ImGuiTabBar* tab_bar = g.TabBars.TryGetMapData(n)) + { + PushID(tab_bar); + DebugNodeTabBar(tab_bar, "TabBar"); + PopID(); + } TreePop(); } // Details for Tables -#ifdef IMGUI_HAS_TABLE - if (TreeNode("Tables", "Tables (%d)", g.Tables.GetSize())) + if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount())) + { + for (int n = 0; n < g.Tables.GetMapSize(); n++) + if (ImGuiTable* table = g.Tables.TryGetMapData(n)) + DebugNodeTable(table); + TreePop(); + } + + // Details for Fonts +#ifndef IMGUI_DISABLE_DEMO_WINDOWS + ImFontAtlas* atlas = g.IO.Fonts; + if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) { - for (int n = 0; n < g.Tables.GetSize(); n++) - DebugNodeTable(g.Tables.GetByIndex(n)); + ShowFontAtlas(atlas); TreePop(); } -#endif // #ifdef IMGUI_HAS_TABLE +#endif // Details for Docking #ifdef IMGUI_HAS_DOCK @@ -16170,14 +16549,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } -#ifdef IMGUI_HAS_TABLE if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) DebugNodeTableSettings(settings); TreePop(); } -#endif // #ifdef IMGUI_HAS_TABLE #ifdef IMGUI_HAS_DOCK if (TreeNode("SettingsDocking", "Settings packed data: Docking")) @@ -16192,14 +16569,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImGuiDockNodeSettings* settings = &dc->NodesSettings[n]; const char* selected_tab_name = NULL; - if (settings->SelectedWindowId) + if (settings->SelectedTabId) { - if (ImGuiWindow* window = FindWindowByID(settings->SelectedWindowId)) + if (ImGuiWindow* window = FindWindowByID(settings->SelectedTabId)) selected_tab_name = window->Name; - else if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->SelectedWindowId)) + else if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->SelectedTabId)) selected_tab_name = window_settings->GetName(); } - BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedWindowId, selected_tab_name ? selected_tab_name : settings->SelectedWindowId ? "N/A" : ""); + BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedTabId, selected_tab_name ? selected_tab_name : settings->SelectedTabId ? "N/A" : ""); } TreePop(); } @@ -16216,12 +16593,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Misc Details if (TreeNode("Internal state")) { - const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Nav", "Clipboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); Text("WINDOWING"); Indent(); Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); - Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL"); Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL"); Text("HoveredDockNode: 0x%08X", g.HoveredDockNode ? g.HoveredDockNode->ID : 0); Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); @@ -16232,7 +16609,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) Indent(); Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); - Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + Text("ActiveIdUsing: Wheel: %d, NavDirMask: %X, NavInputMask: %X, KeyInputMask: %llX", g.ActiveIdUsingMouseWheel, g.ActiveIdUsingNavDirMask, g.ActiveIdUsingNavInputMask, g.ActiveIdUsingKeyInputMask); + Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); Unindent(); @@ -16276,14 +16654,13 @@ void ImGui::ShowMetricsWindow(bool* p_open) } } -#ifdef IMGUI_HAS_TABLE // Overlay: Display Tables Rectangles if (cfg->ShowTablesRects) { - for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { - ImGuiTable* table = g.Tables.GetByIndex(table_n); - if (table->LastFrameActive < g.FrameCount - 1) + ImGuiTable* table = g.Tables.TryGetMapData(table_n); + if (table == NULL || table->LastFrameActive < g.FrameCount - 1) continue; ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow); if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) @@ -16293,7 +16670,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; - draw_list->AddRect(r.Min, r.Max, col, 0.0f, ~0, thickness); + draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness); } } else @@ -16303,7 +16680,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) } } } -#endif // #ifdef IMGUI_HAS_TABLE #ifdef IMGUI_HAS_DOCK // Overlay: Display Docking info @@ -16328,6 +16704,25 @@ void ImGui::ShowMetricsWindow(bool* p_open) End(); } +// [DEBUG] List fonts in a font atlas and display its texture +void ImGui::ShowFontAtlas(ImFontAtlas* atlas) +{ + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + PushID(font); + DebugNodeFont(font); + PopID(); + } + if (TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); + Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col); + TreePop(); + } +} + // [DEBUG] Display contents of Columns void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) { @@ -16339,6 +16734,34 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) TreePop(); } +static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled) +{ + using namespace ImGui; + PushID(label); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + Text("%s:", label); + if (!enabled) + PushDisabled(); + CheckboxFlags("NoSplit", p_flags, ImGuiDockNodeFlags_NoSplit); + CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize); + CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX); + CheckboxFlags("NoResizeY",p_flags, ImGuiDockNodeFlags_NoResizeY); + CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar); + CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar); + CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton); + CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton); + CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking); + CheckboxFlags("NoDockingSplitMe", p_flags, ImGuiDockNodeFlags_NoDockingSplitMe); + CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther); + CheckboxFlags("NoDockingOverMe", p_flags, ImGuiDockNodeFlags_NoDockingOverMe); + CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther); + CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty); + if (!enabled) + PopDisabled(); + PopStyleVar(); + PopID(); +} + // [DEBUG] Display contents of ImDockNode void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) { @@ -16369,17 +16792,16 @@ void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) node->IsCentralNode() ? " IsCentralNode" : "", is_alive ? " IsAlive" : "", is_active ? " IsActive" : "", node->WantLockSizeOnce ? " WantLockSizeOnce" : ""); - if (TreeNode("flags", "LocalFlags: 0x%04X SharedFlags: 0x%04X", node->LocalFlags, node->SharedFlags)) + if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags)) { - CheckboxFlags("LocalFlags: NoDocking", &node->LocalFlags, ImGuiDockNodeFlags_NoDocking); - CheckboxFlags("LocalFlags: NoSplit", &node->LocalFlags, ImGuiDockNodeFlags_NoSplit); - CheckboxFlags("LocalFlags: NoResize", &node->LocalFlags, ImGuiDockNodeFlags_NoResize); - CheckboxFlags("LocalFlags: NoResizeX", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeX); - CheckboxFlags("LocalFlags: NoResizeY", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeY); - CheckboxFlags("LocalFlags: NoTabBar", &node->LocalFlags, ImGuiDockNodeFlags_NoTabBar); - CheckboxFlags("LocalFlags: HiddenTabBar", &node->LocalFlags, ImGuiDockNodeFlags_HiddenTabBar); - CheckboxFlags("LocalFlags: NoWindowMenuButton", &node->LocalFlags, ImGuiDockNodeFlags_NoWindowMenuButton); - CheckboxFlags("LocalFlags: NoCloseButton", &node->LocalFlags, ImGuiDockNodeFlags_NoCloseButton); + if (BeginTable("flags", 4)) + { + TableNextColumn(); DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true); + EndTable(); + } TreePop(); } if (node->ParentNode) @@ -16480,7 +16902,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con { ImDrawListFlags backup_flags = fg_draw_list->Flags; fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. - fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), true, 1.0f); + fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); fg_draw_list->Flags = backup_flags; } } @@ -16507,7 +16929,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co for (int n = 0; n < 3; n++, idx_n++) vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); if (show_mesh) - out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), true, 1.0f); // In yellow: mesh triangles + out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles } // Draw bounding boxes if (show_aabb) @@ -16518,6 +16940,102 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->Flags = backup_flags; } +// [DEBUG] Display details for a single font, called by ShowStyleEditor(). +void ImGui::DebugNodeFont(ImFont* font) +{ + bool opened = TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", + font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); + SameLine(); + if (SmallButton("Set as default")) + GetIO().FontDefault = font; + if (!opened) + return; + + // Display preview text + PushFont(font); + Text("The quick brown fox jumps over the lazy dog"); + PopFont(); + + // Display details + SetNextItemWidth(GetFontSize() * 8); + DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); + SameLine(); MetricsHelpMarker( + "Note than the default embedded font is NOT meant to be scaled.\n\n" + "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " + "You may oversample them to get some flexibility with scaling. " + "You can also render at multiple sizes and select which one to use at runtime.\n\n" + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); + Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + char c_str[5]; + Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); + Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); + const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); + Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + if (font->ConfigData) + if (const ImFontConfig* cfg = &font->ConfigData[config_i]) + BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + + // Display all glyphs of the fonts in separate pages of 256 characters + if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = font->FontSize * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) + { + base += 4096 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (font->FindGlyphNoFallback((ImWchar)(base + n))) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (glyph) + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (glyph && IsMouseHoveringRect(cell_p1, cell_p2)) + { + BeginTooltip(); + Text("Codepoint: U+%04X", base + n); + Separator(); + Text("Visible: %d", glyph->Visible); + Text("AdvanceX: %.1f", glyph->AdvanceX); + Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); + } + TreePop(); + } + TreePop(); +} + // [DEBUG] Display contents of ImGuiStorage void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) { @@ -16549,7 +17067,7 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) } p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } "); if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } - bool open = TreeNode(tab_bar, "%s", buf); + bool open = TreeNode(label, "%s", buf); if (!is_active) { PopStyleColor(); } if (is_active && IsItemHovered()) { @@ -16639,19 +17157,28 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : ""); BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems); - BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); - BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); - if (!window->NavRectRel[0].IsInverted()) - BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); - else - BulletText("NavRectRel[0]: "); + for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) + { + ImRect r = window->NavRectRel[layer]; + if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + { + BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); + continue; + } + BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); + if (IsItemHovered()) + GetForegroundDrawList(window)->AddRect(r.Min + window->Pos, r.Max + window->Pos, IM_COL32(255, 255, 0, 255)); + } + BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "", window->ViewportId, window->ViewportPos.x, window->ViewportPos.y); BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1); BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible); if (window->DockNode || window->DockNodeAsHost) DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode"); + if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } - if (window->RootWindowDockStop != window->RootWindow) { DebugNodeWindow(window->RootWindowDockStop, "RootWindowDockStop"); } + if (window->RootWindowDockTree != window->RootWindow) { DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree"); } if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); } if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) @@ -16687,9 +17214,11 @@ void ImGui::DebugNodeWindowsList(ImVector* windows, const char* la #else void ImGui::ShowMetricsWindow(bool*) {} +void ImGui::ShowFontAtlas(ImFontAtlas*) {} void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} +void ImGui::DebugNodeFont(ImFont*) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} diff --git a/examples/interactive/imgui-1.81/imgui.h b/examples/interactive/imgui-1.83/imgui.h similarity index 93% rename from examples/interactive/imgui-1.81/imgui.h rename to examples/interactive/imgui-1.83/imgui.h index 48b71756..e673cd87 100644 --- a/examples/interactive/imgui-1.81/imgui.h +++ b/examples/interactive/imgui-1.83/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (headers) // Help: @@ -11,10 +11,11 @@ // - FAQ http://dearimgui.org/faq // - Homepage & latest https://github.com/ocornut/imgui // - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/3488 (please post your screenshots/video there!) +// - Gallery https://github.com/ocornut/imgui/issues/3793 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Wiki https://github.com/ocornut/imgui/wiki // - Issues & support https://github.com/ocornut/imgui/issues +// - Discussions https://github.com/ocornut/imgui/discussions /* @@ -27,12 +28,12 @@ Index of this file: // [SECTION] ImGuiStyle // [SECTION] ImGuiIO // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiWindowClass, ImGuiPayload, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) -// [SECTION] Obsolete functions // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) -// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] Platform interface for multi-viewport support (ImGuiPlatformIO, ImGuiPlatformMonitor) +// [SECTION] Obsolete functions and types */ @@ -60,8 +61,8 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) -#define IMGUI_VERSION "1.81" -#define IMGUI_VERSION_NUM 18100 +#define IMGUI_VERSION "1.84 WIP" +#define IMGUI_VERSION_NUM 18313 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch @@ -91,18 +92,31 @@ Index of this file: #endif // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. -#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__clang__) -#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) -#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) -#elif !defined(IMGUI_USE_STB_SPRINTF) && defined(__GNUC__) && defined(__MINGW32__) +#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) #define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) #define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) +#elif !defined(IMGUI_USE_STB_SPRINTF) && (defined(__clang__) || defined(__GNUC__)) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) #else #define IM_FMTARGS(FMT) #define IM_FMTLIST(FMT) #endif +// Disable some of MSVC most aggressive Debug runtime checks in function header/footer (used in some simple/low-level functions) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(IMGUI_DEBUG_PARANOID) +#define IM_MSVC_RUNTIME_CHECKS_OFF __pragma(runtime_checks("",off)) __pragma(check_stack(off)) __pragma(strict_gs_check(push,off)) +#define IM_MSVC_RUNTIME_CHECKS_RESTORE __pragma(runtime_checks("",restore)) __pragma(check_stack()) __pragma(strict_gs_check(pop)) +#else +#define IM_MSVC_RUNTIME_CHECKS_OFF +#define IM_MSVC_RUNTIME_CHECKS_RESTORE +#endif + // Warnings +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#endif #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wold-style-cast" @@ -167,8 +181,8 @@ typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() -typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect(), AddRectFilled() etc. -typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList +typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions +typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() @@ -200,6 +214,8 @@ typedef void* ImTextureID; // User data for rendering backend to identi typedef unsigned int ImGuiID; // A unique ID used by widgets, typically hashed from a stack of string. typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText() typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints() +typedef void* (*ImGuiMemAllocFunc)(size_t sz, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() +typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() // Character types // (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display) @@ -231,6 +247,7 @@ typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) #endif // 2D vector (often used to store positions or sizes) +IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { float x, y; @@ -253,6 +270,7 @@ struct ImVec4 IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4. #endif }; +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] Dear ImGui end-user API functions @@ -262,8 +280,9 @@ struct ImVec4 namespace ImGui { // Context creation and access - // Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between imgui contexts. - // None of those functions is reliant on the current context. + // - Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between contexts. + // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() + // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for details. IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = destroy current context IMGUI_API ImGuiContext* GetCurrentContext(); @@ -333,7 +352,8 @@ namespace ImGui IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) IMGUI_API ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window. - // Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). + // Window manipulation + // - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0, 0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Sizes will be rounded down. Use callback to apply non-trivial programmatic constraints. @@ -346,7 +366,7 @@ namespace ImGui IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). - IMGUI_API void SetWindowFontScale(float scale); // set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). + IMGUI_API void SetWindowFontScale(float scale); // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state @@ -382,7 +402,7 @@ namespace ImGui IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); // modify a style ImVec2 variable. always use this if you modify the style after NewFrame(). IMGUI_API void PopStyleVar(int count = 1); - IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets IMGUI_API void PopAllowKeyboardFocus(); IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. IMGUI_API void PopButtonRepeat(); @@ -396,6 +416,7 @@ namespace ImGui IMGUI_API void PopTextWrapPos(); // Style read access + // - Use the style editor (ShowStyleEditor() function) to interactively see what the colors are) IMGUI_API ImFont* GetFont(); // get current font IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API @@ -436,11 +457,15 @@ namespace ImGui IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) // ID stack/scopes - // - Read the FAQ for more details about how ID are handled in dear imgui. If you are creating widgets in a loop you most - // likely want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. - // - The resulting ID are hashes of the entire stack. + // Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui. + // - Those questions are answered and impacted by understanding of the ID stack system: + // - "Q: Why is my widget not reacting when I click on it?" + // - "Q: How can I have widgets with an empty label?" + // - "Q: How can I have multiple widgets with the same label?" + // - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely + // want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. // - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others. - // - In this header file we use the "label"/"name" terminology to denote a string that will be displayed and used as an ID, + // - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID, // whereas "str_id" denote a string that is only used as an ID and not normally displayed. IMGUI_API void PushID(const char* str_id); // push string into the ID stack (will hash string). IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); // push string into the ID stack (will hash string). @@ -513,8 +538,8 @@ namespace ImGui IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL, ImGuiSliderFlags flags = 0); - IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); - IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); // Widgets: Regular Sliders // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped and can go off-bounds. @@ -620,13 +645,14 @@ namespace ImGui // - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar. // - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it. // - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it. + // - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment. IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! - IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL // Tooltips @@ -644,30 +670,36 @@ namespace ImGui // - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered(). // - IMPORTANT: Popup identifiers are relative to the current ID stack, so OpenPopup and BeginPopup generally needs to be at the same level of the stack. // This is sometimes leading to confusing mistakes. May rework this in the future. + // Popups: begin/end functions // - BeginPopup(): query popup state, if open start appending into the window. Call EndPopup() afterwards. ImGuiWindowFlags are forwarded to the window. // - BeginPopupModal(): block every interactions behind the window, cannot be closed by user, add a dimming background, has a title bar. IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // return true if the modal is open, and you can start outputting to it. IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + // Popups: open/close functions // - OpenPopup(): set popup state to open. ImGuiPopupFlags are available for opening options. // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. // - CloseCurrentPopup(): use inside the BeginPopup()/EndPopup() scope to close manually. // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). + // - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened. IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). - IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. return true when just opened. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks + IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. + // Popups: open+begin combined functions helpers // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. // - They are convenient to easily create context menus, hence the name. // - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future. // - IMPORTANT: we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight. - IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). - // Popups: test function + + // Popups: query functions // - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack. // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId: return true if any popup is open at the current BeginPopup() level of the popup stack. // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open. @@ -703,6 +735,7 @@ namespace ImGui IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. + // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. // - Use TableHeadersRow() to create a header row and automatically submit a TableHeader() for each column. @@ -711,17 +744,19 @@ namespace ImGui // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in // some advanced use cases (e.g. adding custom widgets in header row). // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. - IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImU32 user_id = 0); + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0); IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) + // Tables: Sorting // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. NULL when not sorting. // - When 'SpecsDirty == true' you should sort your data. It will be true when sorting specs have changed // since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting, else you may // wastefully sort your data every frame! // - Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). - IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + // Tables: Miscellaneous functions // - Functions args 'int column_n' treat the default value of -1 as the same as passing the current column index. IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) @@ -729,9 +764,10 @@ namespace ImGui IMGUI_API int TableGetRowIndex(); // return current row index. IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. + IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) IMGUI_API void TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. - // Legacy Columns API (2020: prefer using Tables!) + // Legacy Columns API (prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished @@ -754,15 +790,19 @@ namespace ImGui // Docking // [BETA API] Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable. // Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! - // - To dock windows: if io.ConfigDockingWithShift == false (default) drag window from their title bar. - // - To dock windows: if io.ConfigDockingWithShift == true: hold SHIFT anywhere while moving windows. - // About DockSpace: + // - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. + // - Drag from window menu button (upper-left button) to undock an entire node (all windows). + // About dockspaces: // - Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details. - // - DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app. - IMGUI_API void DockSpace(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); + // - Use DockSpaceOverViewport() to create an explicit dock node covering the screen or a specific viewport. + // This is often used with ImGuiDockNodeFlags_PassthruCentralNode. + // - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame! + // - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. + // e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. + IMGUI_API ImGuiID DockSpace(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); IMGUI_API ImGuiID DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); - IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id (FIXME-DOCK) - IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (rare/advanced uses: provide hints to the platform backend via altered viewport flags and parent/child info) + IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id + IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship) IMGUI_API ImGuiID GetWindowDockID(); IMGUI_API bool IsWindowDocked(); // is current window docked into another window? @@ -774,10 +814,14 @@ namespace ImGui IMGUI_API void LogFinish(); // stop logging (close file, etc.) IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + IMGUI_API void LogTextV(const char* fmt, va_list args) IM_FMTLIST(1); // Drag and Drop - // - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback "..." tooltip as replacement) - IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + // - On source items, call BeginDragDropSource(), if it returns true also call SetDragDropPayload() + EndDragDropSource(). + // - On target candidates, call BeginDragDropTarget(), if it returns true also call AcceptDragDropPayload() + EndDragDropTarget(). + // - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback "..." tooltip, see #1725) + // - An item can be both drag source and drop target. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0); // call after submitting an item which may be dragged. when this return true, you can call SetDragDropPayload() + EndDragDropSource() IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t sz, ImGuiCond cond = 0); // type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive a payload. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() @@ -795,13 +839,13 @@ namespace ImGui IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. - // Item/Widgets Utilities - // - Most of the functions are referring to the last/previous item we submitted. + // Item/Widgets Utilities and Query Functions + // - Most of the functions are referring to the previous Item that has been submitted. // - See Demo Window under "Widgets->Querying Status" for an interactive visualization of most of those functions. IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? - IMGUI_API bool IsItemClicked(ImGuiMouseButton mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) == IsMouseClicked(mouse_button) && IsItemHovered() + IMGUI_API bool IsItemClicked(ImGuiMouseButton mouse_button = 0); // is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this it NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets. IMGUI_API bool IsItemActivated(); // was the last item just made active (item was previously inactive). @@ -820,7 +864,7 @@ namespace ImGui // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. // - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. - IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. + IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL. // Miscellaneous Utilities IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. @@ -886,18 +930,22 @@ namespace ImGui // Settings/.Ini Utilities // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. + // - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables). IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. // Debug Utilities + // - This is used by the IMGUI_CHECKVERSION() macro. IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert, size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro. // Memory Allocators - // - All those functions are not reliant on the current context. - // - If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again because we use global storage for those. - IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = NULL); + // - Those functions are not reliant on the current context. + // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() + // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. + IMGUI_API void SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data = NULL); + IMGUI_API void GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data); IMGUI_API void* MemAlloc(size_t size); IMGUI_API void MemFree(void* ptr); @@ -905,7 +953,6 @@ namespace ImGui // Read comments around the ImGuiPlatformIO structure for more details. // Note: You may use GetWindowViewport() to get the current viewport of the current window. IMGUI_API ImGuiPlatformIO& GetPlatformIO(); // platform/renderer functions, for backend to setup + viewports list. - IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. In the future in "no main platform window" mode we will direct this to primary monitor. IMGUI_API void UpdatePlatformWindows(); // call in main loop. will call CreateWindow/ResizeWindow/etc. platform functions for each secondary viewport, and DestroyWindow for each inactive viewport. IMGUI_API void RenderPlatformWindowsDefault(void* platform_render_arg = NULL, void* renderer_render_arg = NULL); // call in main loop. will call RenderWindow/SwapBuffers platform functions for each secondary viewport which doesn't have the ImGuiViewportFlags_Minimized flag set. May be reimplemented by user for custom rendering needs. IMGUI_API void DestroyPlatformWindows(); // call DestroyWindow platform functions for all viewports. call from backend Shutdown() if you need to close platform windows before imgui shutdown. otherwise will be called by DestroyContext(). @@ -941,7 +988,7 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) - ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. When used in a tab/docking context, tab is selected on closure and closure is deferred by one frame to allow code to cancel the closure (with a confirmation popup, etc.) without flicker. + ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. ImGuiWindowFlags_NoDocking = 1 << 21, // Disable docking of this window ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, @@ -978,16 +1025,18 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally - ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_AlwaysOverwrite = 1 << 13, // Overwrite mode ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' 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_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) - ImGuiInputTextFlags_CallbackEdit = 1 << 19, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) - // [Internal] - ImGuiInputTextFlags_Multiline = 1 << 20, // For internal use by InputTextMultiline() - ImGuiInputTextFlags_NoMarkEdited = 1 << 21 // For internal use by functions using InputText() before reformatting data + ImGuiInputTextFlags_CallbackEdit = 1 << 19 // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + + // Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior +#endif }; // Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() @@ -1080,7 +1129,7 @@ enum ImGuiTabBarFlags_ enum ImGuiTabItemFlags_ { ImGuiTabItemFlags_None = 0, - ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. Also: tab is selected on closure and closure is deferred by one frame to allow code to undo it without flicker. + ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Display a dot next to the title + tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem() ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() @@ -1174,28 +1223,30 @@ enum ImGuiTableColumnFlags_ { // Input configuration flags ImGuiTableColumnFlags_None = 0, - ImGuiTableColumnFlags_DefaultHide = 1 << 0, // Default as a hidden/disabled column. - ImGuiTableColumnFlags_DefaultSort = 1 << 1, // Default as a sorting column. - ImGuiTableColumnFlags_WidthStretch = 1 << 2, // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp). - ImGuiTableColumnFlags_WidthFixed = 1 << 3, // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable). - ImGuiTableColumnFlags_NoResize = 1 << 4, // Disable manual resizing. - ImGuiTableColumnFlags_NoReorder = 1 << 5, // Disable manual reordering this column, this will also prevent other columns from crossing over this column. - ImGuiTableColumnFlags_NoHide = 1 << 6, // Disable ability to hide/disable this column. - ImGuiTableColumnFlags_NoClip = 1 << 7, // Disable clipping for this column (all NoClip columns will render in a same draw command). - ImGuiTableColumnFlags_NoSort = 1 << 8, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). - ImGuiTableColumnFlags_NoSortAscending = 1 << 9, // Disable ability to sort in the ascending direction. - ImGuiTableColumnFlags_NoSortDescending = 1 << 10, // Disable ability to sort in the descending direction. - ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Disable header text width contribution to automatic column width. - ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). - ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. - ImGuiTableColumnFlags_IndentEnable = 1 << 14, // Use current Indent value when entering cell (default for column 0). - ImGuiTableColumnFlags_IndentDisable = 1 << 15, // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. + ImGuiTableColumnFlags_Disabled = 1 << 0, // Overriding/master disable flag: hide column, won't show in context menu (unlike calling TableSetColumnEnabled() which manipulates the user accessible state) + ImGuiTableColumnFlags_DefaultHide = 1 << 1, // Default as a hidden/disabled column. + ImGuiTableColumnFlags_DefaultSort = 1 << 2, // Default as a sorting column. + ImGuiTableColumnFlags_WidthStretch = 1 << 3, // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp). + ImGuiTableColumnFlags_WidthFixed = 1 << 4, // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable). + ImGuiTableColumnFlags_NoResize = 1 << 5, // Disable manual resizing. + ImGuiTableColumnFlags_NoReorder = 1 << 6, // Disable manual reordering this column, this will also prevent other columns from crossing over this column. + ImGuiTableColumnFlags_NoHide = 1 << 7, // Disable ability to hide/disable this column. + ImGuiTableColumnFlags_NoClip = 1 << 8, // Disable clipping for this column (all NoClip columns will render in a same draw command). + ImGuiTableColumnFlags_NoSort = 1 << 9, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + ImGuiTableColumnFlags_NoSortAscending = 1 << 10, // Disable ability to sort in the ascending direction. + ImGuiTableColumnFlags_NoSortDescending = 1 << 11, // Disable ability to sort in the descending direction. + ImGuiTableColumnFlags_NoHeaderLabel = 1 << 12, // TableHeadersRow() will not submit label for this column. Convenient for some small columns. Name will still appear in context menu. + ImGuiTableColumnFlags_NoHeaderWidth = 1 << 13, // Disable header text width contribution to automatic column width. + ImGuiTableColumnFlags_PreferSortAscending = 1 << 14, // Make the initial sort direction Ascending when first sorting on this column (default). + ImGuiTableColumnFlags_PreferSortDescending = 1 << 15, // Make the initial sort direction Descending when first sorting on this column. + ImGuiTableColumnFlags_IndentEnable = 1 << 16, // Use current Indent value when entering cell (default for column 0). + ImGuiTableColumnFlags_IndentDisable = 1 << 17, // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. // Output status flags, read-only via TableGetColumnFlags() - ImGuiTableColumnFlags_IsEnabled = 1 << 20, // Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. - ImGuiTableColumnFlags_IsVisible = 1 << 21, // Status: is visible == is enabled AND not clipped by scrolling. - ImGuiTableColumnFlags_IsSorted = 1 << 22, // Status: is currently part of the sort specs - ImGuiTableColumnFlags_IsHovered = 1 << 23, // Status: is hovered by mouse + ImGuiTableColumnFlags_IsEnabled = 1 << 24, // Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. + ImGuiTableColumnFlags_IsVisible = 1 << 25, // Status: is visible == is enabled AND not clipped by scrolling. + ImGuiTableColumnFlags_IsSorted = 1 << 26, // Status: is currently part of the sort specs + ImGuiTableColumnFlags_IsHovered = 1 << 27, // Status: is hovered by mouse // [Internal] Combinations and masks ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed, @@ -1589,13 +1640,13 @@ enum ImGuiColorEditFlags_ // Defaults Options. You can set application defaults using SetColorEditOptions(). The intent is that you probably don't want to // override them in most of your calls. Let the user choose via the option menu and/or call SetColorEditOptions() once during startup. - ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, // [Internal] Masks - ImGuiColorEditFlags__DisplayMask = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, - ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, - ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, - ImGuiColorEditFlags__InputMask = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV + ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, + ImGuiColorEditFlags_DataTypeMask_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, + ImGuiColorEditFlags_PickerMask_ = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags_InputMask_ = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1689,6 +1740,7 @@ template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p // Do NOT use this class as a std::vector replacement in your own code! Many of the structures used by dear imgui can be safely initialized by a zero-memset. //----------------------------------------------------------------------------- +IM_MSVC_RUNTIME_CHECKS_OFF template struct ImVector { @@ -1705,7 +1757,11 @@ struct ImVector inline ImVector() { Size = Capacity = 0; Data = NULL; } inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } - inline ~ImVector() { if (Data) IM_FREE(Data); } + inline ~ImVector() { if (Data) IM_FREE(Data); } // Important: does not destruct anything + + inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } // Important: does not destruct anything + inline void clear_delete() { for (int n = 0; n < Size; n++) IM_DELETE(Data[n]); clear(); } // Important: never called automatically! always explicit. + inline void clear_destruct() { for (int n = 0; n < Size; n++) Data[n].~T(); clear(); } // Important: never called automatically! always explicit. inline bool empty() const { return Size == 0; } inline int size() const { return Size; } @@ -1715,7 +1771,6 @@ struct ImVector inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } - inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } inline T* begin() { return Data; } inline const T* begin() const { return Data; } inline T* end() { return Data + Size; } @@ -1747,6 +1802,7 @@ struct ImVector inline bool find_erase_unsorted(const T& v) { const T* it = find(v); if (it < Data + Size) { erase_unsorted(it); return true; } return false; } inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; return (int)off; } }; +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] ImGuiStyle @@ -1796,7 +1852,7 @@ struct ImGuiStyle bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float CircleSegmentMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. ImVec4 Colors[ImGuiCol_COUNT]; IMGUI_API ImGuiStyle(); @@ -1821,7 +1877,7 @@ struct ImGuiIO ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size) float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. - const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory. + const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. const char* LogFilename; // = "imgui_log.txt"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified). float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. @@ -1839,7 +1895,6 @@ struct ImGuiIO // Docking options (when ImGuiConfigFlags_DockingEnable is set) bool ConfigDockingNoSplit; // = false // Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars. - bool ConfigDockingWithShift; // = false // Enable docking with holding Shift key (reduce visual noise, allows dropping in wider space) bool ConfigDockingAlwaysTabBar; // = false // [BETA] [FIXME: This currently creates regression with auto-sizing and general overhead] Make every single floating window display within a docking node. bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge. @@ -1911,7 +1966,7 @@ struct ImGuiIO bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving! bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events). - float Framerate; // Application framerate estimate, in frame per second. Solely for convenience. Rolling average estimation based on io.DeltaTime over 120 frames. + float Framerate; // Rough estimate of application framerate, in frame per second. Solely for convenience. Rolling average estimation based on io.DeltaTime over 120 frames. int MetricsRenderVertices; // Vertices output during last call to Render() int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 int MetricsRenderWindows; // Number of visible windows @@ -2014,7 +2069,6 @@ struct ImGuiWindowClass ImGuiViewportFlags ViewportFlagsOverrideClear; // Viewport flags to clear when a window of this class owns a viewport. This allows you to enforce OS decoration or task bar icon, override the defaults on a per-window basis. ImGuiTabItemFlags TabItemFlagsOverrideSet; // [EXPERIMENTAL] TabItem flags to set when a window of this class gets submitted into a dock node tab bar. May use with ImGuiTabItemFlags_Leading or ImGuiTabItemFlags_Trailing. ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) - ImGuiDockNodeFlags DockNodeFlagsOverrideClear; // [EXPERIMENTAL] bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override? @@ -2067,51 +2121,6 @@ struct ImGuiTableSortSpecs ImGuiTableSortSpecs() { memset(this, 0, sizeof(*this)); } }; -//----------------------------------------------------------------------------- -// [SECTION] Obsolete functions -// (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) -// Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. -//----------------------------------------------------------------------------- - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -namespace ImGui -{ - // OBSOLETED in 1.81 (from February 2021) - IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // Helper to calculate size from items_count and height_in_items - static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } - static inline void ListBoxFooter() { EndListBox(); } - // OBSOLETED in 1.79 (from August 2020) - static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! - // OBSOLETED in 1.78 (from June 2020) - // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. - // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. - IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power); - IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power); - static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } - IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power); - IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power); - static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } - static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } - static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } - static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } - // OBSOLETED in 1.77 (from June 2020) - static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } - // OBSOLETED in 1.72 (from April 2019) - static inline void TreeAdvanceToLabelPos() { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } - // OBSOLETED in 1.71 (from June 2019) - static inline void SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } - // OBSOLETED in 1.70 (from May 2019) - static inline float GetContentRegionAvailWidth() { return GetContentRegionAvail().x; } - // OBSOLETED in 1.69 (from Mar 2019) - static inline ImDrawList* GetOverlayDrawList() { return GetForegroundDrawList(); } - // OBSOLETED in 1.66 (from Sep 2018) - static inline void SetScrollHere(float center_ratio=0.5f){ SetScrollHereY(center_ratio); } -} -#endif - //----------------------------------------------------------------------------- // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) //----------------------------------------------------------------------------- @@ -2357,6 +2366,9 @@ struct ImDrawCmd void* UserCallbackData; // 4-8 // The draw callback code can access this. ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + + // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) + inline ImTextureID GetTexID() const { return TextureId; } }; // Vertex index, default to 16-bit @@ -2415,21 +2427,27 @@ struct ImDrawListSplitter IMGUI_API void SetCurrentChannel(ImDrawList* draw_list, int channel_idx); }; -enum ImDrawCornerFlags_ +// Flags for ImDrawList functions +// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused) +enum ImDrawFlags_ { - ImDrawCornerFlags_None = 0, - ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 - ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 - ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 - ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 - ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 - ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC - ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 - ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA - ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience + ImDrawFlags_None = 0, + ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) + ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. + ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. + ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. + ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. + ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. + ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone }; -// Flags for ImDrawList. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. +// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. // It is however possible to temporarily alter flags between calls to ImDrawList:: functions. enum ImDrawListFlags_ { @@ -2489,8 +2507,8 @@ struct ImDrawList // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guaranteed a specific number of sides. IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size), rounding_corners_flags: 4 bits corresponding to which corner to round - IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); @@ -2502,7 +2520,7 @@ struct ImDrawList IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order. IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) @@ -2513,19 +2531,19 @@ struct ImDrawList // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); // Stateful path API, add points then finish with PathFillConvex() or PathStroke() inline void PathClear() { _Path.Size = 0; } inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } // Note: Anti-aliased filling requires points to be in clockwise order. - inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); _Path.Size = 0; } - IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 10); + inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } + IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) - IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); // Advanced IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. @@ -2563,9 +2581,13 @@ struct ImDrawList IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); + IMGUI_API void _TryMergeDrawCmds(); IMGUI_API void _OnChangedClipRect(); IMGUI_API void _OnChangedTextureID(); IMGUI_API void _OnChangedVtxOffset(); + IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; + IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); + IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); }; // All draw data to render a Dear ImGui frame @@ -2711,7 +2733,7 @@ struct ImFontAtlas IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && (TexPixelsAlpha8 != NULL || TexPixelsRGBA32 != NULL); } + bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't built texture but effectively we should check TexID != 0 except that would be backend dependent... void SetTexID(ImTextureID id) { TexID = id; } //------------------------------------------- @@ -2735,11 +2757,12 @@ struct ImFontAtlas //------------------------------------------- // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // After calling Build(), you can query the rectangle position and render your pixels. - // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. - // Read docs/FONTS.md for more details about using colorful icons. - // Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. + // - After calling Build(), you can query the rectangle position and render your pixels. + // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of prefered texture format. + // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), + // so you can render e.g. custom colorful icons and use them as regular glyphs. + // - Read docs/FONTS.md for more details about using colorful icons. + // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. IMGUI_API int AddCustomRectRegular(int width, int height); IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } @@ -2752,14 +2775,16 @@ struct ImFontAtlas // Members //------------------------------------------- - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0. + bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. // [Internal] // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + bool TexReady; // Set when texture was built matching current font input + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 int TexWidth; // Texture width calculated during Build(). @@ -2781,7 +2806,7 @@ struct ImFontAtlas #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ - typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ + //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ #endif }; @@ -2803,8 +2828,9 @@ struct ImFont ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. - ImWchar FallbackChar; // 2 // in // = '?' // Replacement character if a glyph isn't found. Only set via SetFallbackChar() - ImWchar EllipsisChar; // 2 // out // = -1 // Character used for ellipsis rendering. + ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. + ImWchar EllipsisChar; // 2 // out // = '...' // Character used for ellipsis rendering. + ImWchar DotChar; // 2 // out // = '.' // Character used for ellipsis rendering (if a single '...' character isn't found) bool DirtyLookupTables; // 1 // out // float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] @@ -2834,7 +2860,6 @@ struct ImFont IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. IMGUI_API void SetGlyphVisible(ImWchar c, bool visible); - IMGUI_API void SetFallbackChar(ImWchar c); IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; @@ -2872,7 +2897,7 @@ struct ImGuiViewport { ImGuiID ID; // Unique identifier for the viewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ - ImVec2 Pos; // Main Area: Position of the viewport (Dear Imgui coordinates are the same as OS desktop/native coordinates) + ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) @@ -2981,7 +3006,7 @@ struct ImGuiPlatformIO bool (*Platform_GetWindowFocus)(ImGuiViewport* vp); // . . U . . // bool (*Platform_GetWindowMinimized)(ImGuiViewport* vp); // N . . . . // Get platform window minimized state. When minimized, we generally won't attempt to get/set size and contents will be culled more easily void (*Platform_SetWindowTitle)(ImGuiViewport* vp, const char* str); // . . U . . // Set platform window title (given an UTF-8 string) - void (*Platform_SetWindowAlpha)(ImGuiViewport* vp, float alpha); // . . U . . // (Optional) Setup window transparency + void (*Platform_SetWindowAlpha)(ImGuiViewport* vp, float alpha); // . . U . . // (Optional) Setup global transparency (not per-pixel transparency) void (*Platform_UpdateWindow)(ImGuiViewport* vp); // . . U . . // (Optional) Called by UpdatePlatformWindows(). Optional hook to allow the platform backend from doing general book-keeping every frame. void (*Platform_RenderWindow)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Main rendering (platform side! This is often unused, or just setting a "current" context for OpenGL bindings). 'render_arg' is the value passed to RenderPlatformWindowsDefault(). void (*Platform_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Call Present/SwapBuffers (platform side! This is often unused!). 'render_arg' is the value passed to RenderPlatformWindowsDefault(). @@ -3022,6 +3047,76 @@ struct ImGuiPlatformMonitor ImGuiPlatformMonitor() { MainPos = MainSize = WorkPos = WorkSize = ImVec2(0, 0); DpiScale = 1.0f; } }; +//----------------------------------------------------------------------------- +// [SECTION] Obsolete functions and types +// (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +// Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.81 (from February 2021) + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // Helper to calculate size from items_count and height_in_items + static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } + static inline void ListBoxFooter() { EndListBox(); } + // OBSOLETED in 1.79 (from August 2020) + static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! + // OBSOLETED in 1.78 (from June 2020) + // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. + // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. + IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power); + IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power); + static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } + IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power); + IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power); + static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } + static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } + static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } + static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } + // OBSOLETED in 1.77 (from June 2020) + static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } + // OBSOLETED in 1.72 (from April 2019) + static inline void TreeAdvanceToLabelPos() { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } + // OBSOLETED in 1.71 (from June 2019) + static inline void SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } + // OBSOLETED in 1.70 (from May 2019) + static inline float GetContentRegionAvailWidth() { return GetContentRegionAvail().x; } + + // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + //static inline ImDrawList* GetOverlayDrawList() { return GetForegroundDrawList(); } // OBSOLETED in 1.69 (from Mar 2019) + //static inline void SetScrollHere(float ratio = 0.5f) { SetScrollHereY(ratio); } // OBSOLETED in 1.66 (from Nov 2018) + //static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } // OBSOLETED in 1.63 (from Aug 2018) + //static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } // OBSOLETED in 1.60 (from Apr 2018) + //static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } // OBSOLETED in 1.60 (between Dec 2017 and Apr 2018) + //static inline void ShowTestWindow() { return ShowDemoWindow(); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + //static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + //static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + //static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + //static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) +} + +// OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() +typedef ImDrawFlags ImDrawCornerFlags; +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_None = ImDrawFlags_RoundCornersNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit + ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundCornersTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). + ImDrawCornerFlags_TopRight = ImDrawFlags_RoundCornersTopRight, // Was == 0x02 (1 << 1) prior to 1.82. + ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundCornersBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. + ImDrawCornerFlags_BotRight = ImDrawFlags_RoundCornersBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. + ImDrawCornerFlags_All = ImDrawFlags_RoundCornersAll, // Was == 0x0F prior to 1.82 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight +}; + +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //----------------------------------------------------------------------------- #if defined(__clang__) @@ -3030,6 +3125,10 @@ struct ImGuiPlatformMonitor #pragma GCC diagnostic pop #endif +#ifdef _MSC_VER +#pragma warning (pop) +#endif + // Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) #ifdef IMGUI_INCLUDE_IMGUI_USER_H #include "imgui_user.h" diff --git a/examples/interactive/imgui-1.81/imgui_demo.cpp b/examples/interactive/imgui-1.83/imgui_demo.cpp similarity index 96% rename from examples/interactive/imgui-1.81/imgui_demo.cpp rename to examples/interactive/imgui-1.83/imgui_demo.cpp index 96cf24d1..8c9911ef 100644 --- a/examples/interactive/imgui-1.81/imgui_demo.cpp +++ b/examples/interactive/imgui-1.83/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (demo code) // Help: @@ -6,9 +6,9 @@ // - Newcomers, read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // Read imgui.cpp for more details, documentation and comments. -// Get latest version at https://github.com/ocornut/imgui +// Get the latest version at https://github.com/ocornut/imgui -// Message to the person tempted to delete this file when integrating Dear ImGui into their code base: +// Message to the person tempted to delete this file when integrating Dear ImGui into their codebase: // Do NOT remove this file from your project! Think again! It is the most useful reference code that you and other // coders will want to refer to and call. Have the ImGui::ShowDemoWindow() function wired in an always-available // debug menu of your game/app! Removing this file from your project is hindering access to documentation for everyone @@ -16,19 +16,19 @@ // Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). // If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be // linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. -// In other situation, whenever you have Dear ImGui available you probably want this to be available for reference. +// In another situation, whenever you have Dear ImGui available you probably want this to be available for reference. // Thank you, // -Your beloved friend, imgui_demo.cpp (which you won't delete) // Message to beginner C/C++ programmers about the meaning of the 'static' keyword: -// In this demo code, we frequently we use 'static' variables inside functions. A static variable persist across calls, +// In this demo code, we frequently use 'static' variables inside functions. A static variable persists across calls, // so it is essentially like a global variable but declared inside the scope of the function. We do this as a way to // gather code and data in the same place, to make the demo source code faster to read, faster to write, and smaller // in size. It also happens to be a convenient way of storing simple UI related information as long as your function // doesn't need to be reentrant or used in multiple threads. This might be a pattern you will want to use in your code, // but most of the real data you would be editing is likely going to be stored outside your functions. -// The Demo code in this file is designed to be easy to copy-and-paste in into your application! +// The Demo code in this file is designed to be easy to copy-and-paste into your application! // Because of this: // - We never omit the ImGui:: prefix when calling functions, even though most code here is in the same namespace. // - We try to declare static variables in the local scope, as close as possible to the code using them. @@ -93,7 +93,8 @@ Index of this file: // Visual Studio warnings #ifdef _MSC_VER -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #endif // Clang/GCC warnings with -Weverything @@ -136,6 +137,16 @@ Index of this file: #define vsnprintf _vsnprintf #endif +// Format specifiers, printing 64-bit hasn't been decently standardized... +// In a real application you should be using PRId64 and PRIu64 from (non-windows) and on Windows define them yourself. +#ifdef _MSC_VER +#define IM_PRId64 "I64d" +#define IM_PRIu64 "I64u" +#else +#define IM_PRId64 "lld" +#define IM_PRIu64 "llu" +#endif + // Helpers macros // We normally try to not use many helpers in imgui_demo.cpp in order to make code easier to copy and paste, // but making an exception here as those are largely simplifying code... @@ -321,6 +332,7 @@ void ImGui::ShowDemoWindow(bool* p_open) static bool no_background = false; static bool no_bring_to_front = false; static bool no_docking = false; + static bool unsaved_document = false; ImGuiWindowFlags window_flags = 0; if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; @@ -333,6 +345,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; if (no_docking) window_flags |= ImGuiWindowFlags_NoDocking; + if (unsaved_document) window_flags |= ImGuiWindowFlags_UnsavedDocument; if (no_close) p_open = NULL; // Don't pass our bool* to Begin // We specify a default position/size in case there's no data in the .ini file. @@ -446,14 +459,12 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); ImGui::CheckboxFlags("io.ConfigFlags: DockingEnable", &io.ConfigFlags, ImGuiConfigFlags_DockingEnable); - ImGui::SameLine(); HelpMarker(io.ConfigDockingWithShift ? "[beta] Use SHIFT to dock window into each others." : "[beta] Drag from title bar to dock windows into each others."); + ImGui::SameLine(); HelpMarker("Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking.\n\nDrag from window menu button (upper-left button) to undock an entire node (all windows)."); if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { ImGui::Indent(); ImGui::Checkbox("io.ConfigDockingNoSplit", &io.ConfigDockingNoSplit); ImGui::SameLine(); HelpMarker("Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars."); - ImGui::Checkbox("io.ConfigDockingWithShift", &io.ConfigDockingWithShift); - ImGui::SameLine(); HelpMarker("Enable docking when holding Shift only (allows to drop in wider space, reduce visual noise)"); ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar); ImGui::SameLine(); HelpMarker("Create a docking node and tab-bar on single floating windows."); ImGui::Checkbox("io.ConfigDockingTransparentPayload", &io.ConfigDockingTransparentPayload); @@ -552,6 +563,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TableNextColumn(); ImGui::Checkbox("No background", &no_background); ImGui::TableNextColumn(); ImGui::Checkbox("No bring to front", &no_bring_to_front); ImGui::TableNextColumn(); ImGui::Checkbox("No docking", &no_docking); + ImGui::TableNextColumn(); ImGui::Checkbox("Unsaved document", &unsaved_document); ImGui::EndTable(); } } @@ -967,7 +979,7 @@ static void ShowDemoWindowWidgets() // so you can safely copy & paste garbled characters into another application. ImGui::TextWrapped( "CJK text will only appears if the font was loaded with the appropriate CJK character ranges. " - "Call io.Font->AddFontFromFileTTF() manually to load extra character ranges. " + "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. " "Read docs/FONTS.md for details."); ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); @@ -1069,8 +1081,8 @@ static void ShowDemoWindowWidgets() // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; static int item_current_idx = 0; // Here we store our selection data as an index. - const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically it could be anything) - if (ImGui::BeginCombo("combo 1", combo_label, flags)) + const char* combo_preview_value = items[item_current_idx]; // Pass in the preview value visible before opening the combo (it could be anything) + if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { @@ -1086,10 +1098,12 @@ static void ShowDemoWindowWidgets() } // Simplified one-liner Combo() API, using values packed in a single constant string + // This is a convenience for when the selection set is small and known at compile-time. static int item_current_2 = 0; ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); // Simplified one-liner Combo() using an array of const char* + // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control. static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); @@ -1156,7 +1170,7 @@ static void ShowDemoWindowWidgets() static bool selection[5] = { false, true, false, false, false }; ImGui::Selectable("1. I am selectable", &selection[0]); ImGui::Selectable("2. I am selectable", &selection[1]); - ImGui::Text("3. I am not selectable"); + ImGui::Text("(I am not selectable)"); ImGui::Selectable("4. I am selectable", &selection[3]); if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) if (ImGui::IsMouseDoubleClicked(0)) @@ -1206,7 +1220,7 @@ static void ShowDemoWindowWidgets() { static bool selected[10] = {}; - if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) { for (int i = 0; i < 10; i++) { @@ -1217,8 +1231,8 @@ static void ShowDemoWindowWidgets() } ImGui::EndTable(); } - ImGui::Separator(); - if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + ImGui::Spacing(); + if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) { for (int i = 0; i < 10; i++) { @@ -1597,7 +1611,7 @@ static void ShowDemoWindowWidgets() // Plot/Graph widgets are not very good. // Consider writing your own, or using a third-party one, see: // - ImPlot https://github.com/epezent/implot - // - others https://github.com/ocornut/imgui/wiki/Useful-Widgets + // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions if (ImGui::TreeNode("Plots Widgets")) { static bool animate = true; @@ -1646,7 +1660,7 @@ static void ShowDemoWindowWidgets() }; static int func_type = 0, display_count = 70; ImGui::Separator(); - ImGui::SetNextItemWidth(100); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::SameLine(); ImGui::SliderInt("Sample count", &display_count, 1, 400); @@ -1972,12 +1986,12 @@ static void ShowDemoWindowWidgets() ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty,"%u"); ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); - ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%I64d"); - ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%I64d"); - ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%I64d"); - ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%I64u ms"); - ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%I64u ms"); - ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%I64u ms"); + ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%" IM_PRId64); + ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%" IM_PRId64); + ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%" IM_PRId64); + ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%" IM_PRIu64 " ms"); + ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%" IM_PRIu64 " ms"); + ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%" IM_PRIu64 " ms"); ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); ImGui::SliderScalar("slider float low log", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", ImGuiSliderFlags_Logarithmic); ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); @@ -1986,12 +2000,12 @@ static void ShowDemoWindowWidgets() ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams"); ImGui::Text("Sliders (reverse)"); - ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); - ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); + ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); + ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); ImGui::SliderScalar("slider s32 reverse", ImGuiDataType_S32, &s32_v, &s32_fifty, &s32_zero, "%d"); ImGui::SliderScalar("slider u32 reverse", ImGuiDataType_U32, &u32_v, &u32_fifty, &u32_zero, "%u"); - ImGui::SliderScalar("slider s64 reverse", ImGuiDataType_S64, &s64_v, &s64_fifty, &s64_zero, "%I64d"); - ImGui::SliderScalar("slider u64 reverse", ImGuiDataType_U64, &u64_v, &u64_fifty, &u64_zero, "%I64u ms"); + ImGui::SliderScalar("slider s64 reverse", ImGuiDataType_S64, &s64_v, &s64_fifty, &s64_zero, "%" IM_PRId64); + ImGui::SliderScalar("slider u64 reverse", ImGuiDataType_U64, &u64_v, &u64_fifty, &u64_zero, "%" IM_PRIu64 " ms"); static bool inputs_step = true; ImGui::Text("Inputs"); @@ -2225,7 +2239,7 @@ static void ShowDemoWindowWidgets() const char* item_names[] = { "Text", "Button", "Button (w/ repeat)", "Checkbox", "SliderFloat", "InputText", "InputFloat", - "InputFloat3", "ColorEdit4", "MenuItem", "TreeNode", "TreeNode (w/ double-click)", "Combo", "ListBox" + "InputFloat3", "ColorEdit4", "Selectable", "MenuItem", "TreeNode", "TreeNode (w/ double-click)", "Combo", "ListBox" }; static int item_type = 1; ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names)); @@ -2246,11 +2260,12 @@ static void ShowDemoWindowWidgets() if (item_type == 6) { ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); } // Testing +/- buttons on scalar input if (item_type == 7) { ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) if (item_type == 8) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) - if (item_type == 9) { ret = ImGui::MenuItem("ITEM: MenuItem"); } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) - if (item_type == 10){ ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); } // Testing tree node - if (item_type == 11){ ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. - if (item_type == 12){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); } - if (item_type == 13){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + if (item_type == 9) { ret = ImGui::Selectable("ITEM: Selectable"); } // Testing selectable item + if (item_type == 10){ ret = ImGui::MenuItem("ITEM: MenuItem"); } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) + if (item_type == 11){ ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); } // Testing tree node + if (item_type == 12){ ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. + if (item_type == 13){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); } + if (item_type == 14){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } // Display the values of IsItemHovered() and other common item state functions. // Note that the ImGuiHoveredFlags_XXX flags can be combined. @@ -2439,7 +2454,7 @@ static void ShowDemoWindowLayout() // the POV of the parent window). See 'Demo->Querying Status (Active/Focused/Hovered etc.)' for details. { static int offset_x = 0; - ImGui::SetNextItemWidth(100); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::DragInt("Offset X", &offset_x, 1.0f, -1000, 1000); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); @@ -2461,15 +2476,15 @@ static void ShowDemoWindowLayout() if (ImGui::TreeNode("Widgets Width")) { + static float f = 0.0f; + static bool show_indented_items = true; + ImGui::Checkbox("Show indented items", &show_indented_items); + // Use SetNextItemWidth() to set the width of a single upcoming item. // Use PushItemWidth()/PopItemWidth() to set the width of a group of items. // In real code use you'll probably want to choose width values that are proportional to your font size // e.g. Using '20.0f * GetFontSize()' as width instead of '200.0f', etc. - static float f = 0.0f; - static bool show_indented_items = true; - ImGui::Checkbox("Show indented items", &show_indented_items); - ImGui::Text("SetNextItemWidth/PushItemWidth(100)"); ImGui::SameLine(); HelpMarker("Fixed width."); ImGui::PushItemWidth(100); @@ -2885,6 +2900,8 @@ static void ShowDemoWindowLayout() { for (int item = 0; item < 100; item++) { + if (item > 0) + ImGui::SameLine(); if (enable_track && item == track_item) { ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); @@ -2894,7 +2911,6 @@ static void ShowDemoWindowLayout() { ImGui::Text("Item %d", item); } - ImGui::SameLine(); } } float scroll_x = ImGui::GetScrollX(); @@ -3238,46 +3254,84 @@ static void ShowDemoWindowPopups() if (ImGui::TreeNode("Context menus")) { + HelpMarker("\"Context\" functions are simple helpers to associate a Popup to a given Item or Window identifier."); + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: - // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) - // OpenPopup(id); - // return BeginPopup(id); - // For more advanced uses you may want to replicate and customize this code. - // See details in BeginPopupContextItem(). - static float value = 0.5f; - ImGui::Text("Value = %.3f (<-- right-click here)", value); - if (ImGui::BeginPopupContextItem("item context menu")) - { - if (ImGui::Selectable("Set to zero")) value = 0.0f; - if (ImGui::Selectable("Set to PI")) value = 3.1415f; - ImGui::SetNextItemWidth(-FLT_MIN); - ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); - ImGui::EndPopup(); + // if (id == 0) + // id = GetItemID(); // Use last item id + // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) + // OpenPopup(id); + // return BeginPopup(id); + // For advanced advanced uses you may want to replicate and customize this code. + // See more details in BeginPopupContextItem(). + + // Example 1 + // When used after an item that has an ID (e.g. Button), we can skip providing an ID to BeginPopupContextItem(), + // and BeginPopupContextItem() will use the last item ID as the popup ID. + { + const char* names[5] = { "Label1", "Label2", "Label3", "Label4", "Label5" }; + for (int n = 0; n < 5; n++) + { + ImGui::Selectable(names[n]); + if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id + { + ImGui::Text("This a popup for \"%s\"!", names[n]); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Right-click to open popup"); + } } - // We can also use OpenPopupOnItemClick() which is the same as BeginPopupContextItem() but without the - // Begin() call. So here we will make it that clicking on the text field with the right mouse button (1) - // will toggle the visibility of the popup above. - ImGui::Text("(You can also right-click me to open the same popup as above.)"); - ImGui::OpenPopupOnItemClick("item context menu", 1); - - // When used after an item that has an ID (e.g.Button), we can skip providing an ID to BeginPopupContextItem(). - // BeginPopupContextItem() will use the last item ID as the popup ID. - // In addition here, we want to include your editable label inside the button label. - // We use the ### operator to override the ID (read FAQ about ID for details) - static char name[32] = "Label1"; - char buf[64]; - sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label - ImGui::Button(buf); - if (ImGui::BeginPopupContextItem()) + // Example 2 + // Popup on a Text() element which doesn't have an identifier: we need to provide an identifier to BeginPopupContextItem(). + // Using an explicit identifier is also convenient if you want to activate the popups from different locations. { - ImGui::Text("Edit name:"); - ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); + HelpMarker("Text() elements don't have stable identifiers so we need to provide one."); + static float value = 0.5f; + ImGui::Text("Value = %.3f <-- (1) right-click this value", value); + if (ImGui::BeginPopupContextItem("my popup")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::EndPopup(); + } + + // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup. + // Here we make it that right-clicking this other text element opens the same popup as above. + // The popup itself will be submitted by the code above. + ImGui::Text("(2) Or right-click this text"); + ImGui::OpenPopupOnItemClick("my popup", ImGuiPopupFlags_MouseButtonRight); + + // Back to square one: manually open the same popup. + if (ImGui::Button("(3) Or click this button")) + ImGui::OpenPopup("my popup"); + } + + // Example 3 + // When using BeginPopupContextItem() with an implicit identifier (NULL == use last item ID), + // we need to make sure your item identifier is stable. + // In this example we showcase altering the item label while preserving its identifier, using the ### operator (see FAQ). + { + HelpMarker("Showcase using a popup ID linked to item ID, with the item having a changing label + stable ID using the ### operator."); + static char name[32] = "Label1"; + char buf[64]; + sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); } - ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); ImGui::TreePop(); } @@ -3503,6 +3557,7 @@ static void EditTableSizingFlags(ImGuiTableFlags* p_flags) static void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags) { + ImGui::CheckboxFlags("_Disabled", p_flags, ImGuiTableColumnFlags_Disabled); ImGui::SameLine(); HelpMarker("Master disable flag (also hide from context menu)"); ImGui::CheckboxFlags("_DefaultHide", p_flags, ImGuiTableColumnFlags_DefaultHide); ImGui::CheckboxFlags("_DefaultSort", p_flags, ImGuiTableColumnFlags_DefaultSort); if (ImGui::CheckboxFlags("_WidthStretch", p_flags, ImGuiTableColumnFlags_WidthStretch)) @@ -3516,6 +3571,7 @@ static void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags) ImGui::CheckboxFlags("_NoSort", p_flags, ImGuiTableColumnFlags_NoSort); ImGui::CheckboxFlags("_NoSortAscending", p_flags, ImGuiTableColumnFlags_NoSortAscending); ImGui::CheckboxFlags("_NoSortDescending", p_flags, ImGuiTableColumnFlags_NoSortDescending); + ImGui::CheckboxFlags("_NoHeaderLabel", p_flags, ImGuiTableColumnFlags_NoHeaderLabel); ImGui::CheckboxFlags("_NoHeaderWidth", p_flags, ImGuiTableColumnFlags_NoHeaderWidth); ImGui::CheckboxFlags("_PreferSortAscending", p_flags, ImGuiTableColumnFlags_PreferSortAscending); ImGui::CheckboxFlags("_PreferSortDescending", p_flags, ImGuiTableColumnFlags_PreferSortDescending); @@ -4913,6 +4969,9 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + // In this example we'll expose most table flags and settings. + // For specific flags and settings refer to the corresponding section for more detailed explanation. + // This section is mostly useful to experiment with combining certain flags or settings with each others. //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // [DEBUG] if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); @@ -5051,7 +5110,7 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } - // Recreate/reset item list if we changed the number of items + // Update item list if we changed the number of items static ImVector items; static ImVector selection; static bool items_need_sort = false; @@ -5073,6 +5132,7 @@ static void ShowDemoWindowTables() ImVec2 table_scroll_cur, table_scroll_max; // For debug display const ImDrawList* table_draw_list = NULL; // " + // Submit table const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f; if (ImGui::BeginTable("table_advanced", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) { @@ -5131,9 +5191,9 @@ static void ShowDemoWindowTables() const bool item_is_selected = selection.contains(item->ID); ImGui::PushID(item->ID); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); - ImGui::TableNextColumn(); // For the demo purpose we can select among different type of items submitted in the first column + ImGui::TableSetColumnIndex(0); char label[32]; sprintf(label, "%04d", item->ID); if (contents_type == CT_Text) @@ -5164,14 +5224,14 @@ static void ShowDemoWindowTables() } } - if (ImGui::TableNextColumn()) + if (ImGui::TableSetColumnIndex(1)) ImGui::TextUnformatted(item->Name); // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, // and we are currently sorting on the column showing the Quantity. // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. - if (ImGui::TableNextColumn()) + if (ImGui::TableSetColumnIndex(2)) { if (ImGui::SmallButton("Chop")) { item->Quantity += 1; } if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } @@ -5180,16 +5240,16 @@ static void ShowDemoWindowTables() if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } } - if (ImGui::TableNextColumn()) + if (ImGui::TableSetColumnIndex(3)) ImGui::Text("%d", item->Quantity); - ImGui::TableNextColumn(); + ImGui::TableSetColumnIndex(4); if (show_wrapped_text) ImGui::TextWrapped("Lorem ipsum dolor sit amet"); else ImGui::Text("Lorem ipsum dolor sit amet"); - if (ImGui::TableNextColumn()) + if (ImGui::TableSetColumnIndex(5)) ImGui::Text("1234"); ImGui::PopID(); @@ -5456,29 +5516,34 @@ static void ShowDemoWindowMisc() ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos); ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); - // Display Keyboard/Mouse state - if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) + // Display Mouse state + if (ImGui::TreeNode("Mouse State")) { if (ImGui::IsMousePosValid()) ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); else ImGui::Text("Mouse pos: "); ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused + ImGui::TreePop(); + } - ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (0x%X) (%.02f secs)", i, i, io.KeysDownDuration[i]); } - ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } - ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } + // Display Keyboard/Mouse state + if (ImGui::TreeNode("Keyboard & Navigation State")) + { + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyDown(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X) (%.02f secs)", i, i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); - ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. + ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. - ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f (%.02f secs)", i, io.NavInputs[i], io.NavInputsDownDuration[i]); } ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } - ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } ImGui::Button("Hovering me sets the\nkeyboard capture flag"); if (ImGui::IsItemHovered()) @@ -5487,7 +5552,6 @@ static void ShowDemoWindowMisc() ImGui::Button("Holding me clears the\nthe keyboard capture flag"); if (ImGui::IsItemActive()) ImGui::CaptureKeyboardFromApp(false); - ImGui::TreePop(); } @@ -5500,7 +5564,7 @@ static void ShowDemoWindowMisc() ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); ImGui::PushAllowKeyboardFocus(false); ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); - //ImGui::SameLine(); HelpMarker("Use ImGui::PushAllowKeyboardFocus(bool) to disable tabbing through certain widgets."); + ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); ImGui::PopAllowKeyboardFocus(); ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); ImGui::TreePop(); @@ -5526,6 +5590,7 @@ static void ShowDemoWindowMisc() if (focus_3) ImGui::SetKeyboardFocusHere(); ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 3; + ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); ImGui::PopAllowKeyboardFocus(); if (has_focus) @@ -5722,7 +5787,6 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.ConfigViewportsNoDecoration) ImGui::Text("io.ConfigViewportsNoDecoration"); if (io.ConfigViewportsNoDefaultParent) ImGui::Text("io.ConfigViewportsNoDefaultParent"); if (io.ConfigDockingNoSplit) ImGui::Text("io.ConfigDockingNoSplit"); - if (io.ConfigDockingWithShift) ImGui::Text("io.ConfigDockingWithShift"); if (io.ConfigDockingAlwaysTabBar) ImGui::Text("io.ConfigDockingAlwaysTabBar"); if (io.ConfigDockingTransparentPayload) ImGui::Text("io.ConfigDockingTransparentPayload"); if (io.ConfigMacOSXBehaviors) ImGui::Text("io.ConfigMacOSXBehaviors"); @@ -5764,29 +5828,13 @@ void ImGui::ShowAboutWindow(bool* p_open) //----------------------------------------------------------------------------- // [SECTION] Style Editor / ShowStyleEditor() //----------------------------------------------------------------------------- -// - ShowStyleSelector() // - ShowFontSelector() +// - ShowStyleSelector() // - ShowStyleEditor() //----------------------------------------------------------------------------- -// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. -// Here we use the simplified Combo() api that packs items into a single literal string. -// Useful for quick combo boxes where the choices are known locally. -bool ImGui::ShowStyleSelector(const char* label) -{ - static int style_idx = -1; - if (ImGui::Combo(label, &style_idx, "Dark\0Light\0Classic\0")) - { - switch (style_idx) - { - case 0: ImGui::StyleColorsDark(); break; - case 1: ImGui::StyleColorsLight(); break; - case 2: ImGui::StyleColorsClassic(); break; - } - return true; - } - return false; -} +// Forward declare ShowFontAtlas() which isn't worth putting in public API yet +namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } // Demo helper function to select among loaded fonts. // Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. @@ -5814,92 +5862,23 @@ void ImGui::ShowFontSelector(const char* label) "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); } -// [Internal] Display details for a single font, called by ShowStyleEditor(). -static void NodeFont(ImFont* font) +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. +// Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) { - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - bool font_details_opened = ImGui::TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", - font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); - ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { io.FontDefault = font; } - if (!font_details_opened) - return; - - ImGui::PushFont(font); - ImGui::Text("The quick brown fox jumps over the lazy dog"); - ImGui::PopFont(); - ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font - ImGui::SameLine(); HelpMarker( - "Note than the default embedded font is NOT meant to be scaled.\n\n" - "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " - "You may oversample them to get some flexibility with scaling. " - "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); - ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar); - ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar, font->EllipsisChar); - const int surface_sqrt = (int)sqrtf((float)font->MetricsTotalSurface); - ImGui::Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) - if (font->ConfigData) - if (const ImFontConfig* cfg = &font->ConfigData[config_i]) - ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); - if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) - { - // Display all glyphs of the fonts in separate pages of 256 characters - const ImU32 glyph_col = ImGui::GetColorU32(ImGuiCol_Text); - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) - { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) - { - base += 4096 - 256; - continue; - } - - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - float cell_size = font->FontSize * 1; - float cell_spacing = style.ItemSpacing.y; - ImVec2 base_pos = ImGui::GetCursorScreenPos(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - for (unsigned int n = 0; n < 256; n++) - { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (glyph) - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) - { - ImGui::BeginTooltip(); - ImGui::Text("Codepoint: U+%04X", base + n); - ImGui::Separator(); - ImGui::Text("Visible: %d", glyph->Visible); - ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); - ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); - ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); - ImGui::EndTooltip(); - } - } - ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - ImGui::TreePop(); + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Dark\0Light\0Classic\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsDark(); break; + case 1: ImGui::StyleColorsLight(); break; + case 2: ImGui::StyleColorsClassic(); break; } - ImGui::TreePop(); + return true; } - ImGui::TreePop(); + return false; } void ImGui::ShowStyleEditor(ImGuiStyle* ref) @@ -6058,21 +6037,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGuiIO& io = ImGui::GetIO(); ImFontAtlas* atlas = io.Fonts; HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); - ImGui::PushItemWidth(120); - for (int i = 0; i < atlas->Fonts.Size; i++) - { - ImFont* font = atlas->Fonts[i]; - ImGui::PushID(font); - NodeFont(font); - ImGui::PopID(); - } - if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) - { - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); - ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0, 0), ImVec2(1, 1), tint_col, border_col); - ImGui::TreePop(); - } + ImGui::ShowFontAtlas(atlas); // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). @@ -6084,6 +6049,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "Using those settings here will give you poor quality results."); static float window_scale = 1.0f; + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window ImGui::SetWindowFontScale(window_scale); ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything @@ -6103,27 +6069,47 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - ImGui::DragFloat("Circle Segment Max Error", &style.CircleSegmentMaxError, 0.01f, 0.10f, 10.0f, "%.2f"); + ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); if (ImGui::IsItemActive()) { ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); ImGui::BeginTooltip(); - ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::Spacing(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - float RAD_MIN = 10.0f, RAD_MAX = 80.0f; - float off_x = 10.0f; - for (int n = 0; n < 7; n++) + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + for (int n = 0; n < 8; n++) { - const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (7.0f - 1.0f); - draw_list->AddCircle(ImVec2(p.x + off_x + rad, p.y + RAD_MAX), rad, ImGui::GetColorU32(ImGuiCol_Text), 0); - off_x += 10.0f + rad * 2.0f; + const float RAD_MIN = 5.0f; + const float RAD_MAX = 70.0f; + const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); + + ImGui::BeginGroup(); + + ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + + const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); + const float offset_x = floorf(canvas_width * 0.5f); + const float offset_y = floorf(RAD_MAX); + + const ImVec2 p1 = ImGui::GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + + /* + const ImVec2 p2 = ImGui::GetCursorScreenPos(); + draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + */ + + ImGui::EndGroup(); + ImGui::SameLine(); } - ImGui::Dummy(ImVec2(off_x, RAD_MAX * 2.0f)); ImGui::EndTooltip(); } ImGui::SameLine(); @@ -6795,6 +6781,7 @@ static void ShowExampleAppLayout(bool* p_open) ImGui::BeginChild("left pane", ImVec2(150, 0), true); for (int i = 0; i < 100; i++) { + // FIXME: Good candidate to use ImGuiSelectableFlags_SelectOnNav char label[128]; sprintf(label, "MyObject %d", i); if (ImGui::Selectable(label, selected == i)) @@ -7248,9 +7235,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); const float spacing = 10.0f; - const ImDrawCornerFlags corners_none = 0; - const ImDrawCornerFlags corners_all = ImDrawCornerFlags_All; - const ImDrawCornerFlags corners_tl_br = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotRight; + const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; @@ -7262,8 +7247,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) float th = (n == 0) ? 1.0f : thickness; draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, corners_none, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_all, th); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle @@ -7425,13 +7410,23 @@ static void ShowExampleAppCustomRendering(bool* p_open) //----------------------------------------------------------------------------- // Demonstrate using DockSpace() to create an explicit docking node within an existing window. -// Note that you dock windows into each others _without_ a dockspace, by just clicking on -// a window title bar and moving it (+ hold SHIFT if io.ConfigDockingWithShift is set). -// DockSpace() and DockSpaceOverViewport() are only useful to construct a central docking -// location for your application. +// Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! +// - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. +// - Drag from window menu button (upper-left button) to undock an entire node (all windows). +// About dockspaces: +// - Use DockSpace() to create an explicit dock node _within_ an existing window. +// - Use DockSpaceOverViewport() to create an explicit dock node covering the screen or a specific viewport. +// This is often used with ImGuiDockNodeFlags_PassthruCentralNode. +// - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame! (*) +// - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. +// e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. +// (*) because of this constraint, the implicit \"Debug\" window can not be docked into an explicit DockSpace() node, +// because that window is submitted as part of the part of the NewFrame() call. An easy workaround is that you can create +// your own implicit "Debug##2" window after calling DockSpace() and leave it in the window stack for anyone to use. void ShowExampleAppDockSpace(bool* p_open) { - // In 99% case you should be able to just call DockSpaceOverViewport() and ignore all the code below! + // If you strip some features of, this demo is pretty much equivalent to calling DockSpaceOverViewport()! + // In most cases you should be able to just call DockSpaceOverViewport() and ignore all the code below! // In this specific demo, we are not using DockSpaceOverViewport() because: // - we allow the host window to be floating/moveable instead of filling the viewport (when opt_fullscreen == false) // - we allow the host window to have padding (when opt_padding == true) @@ -7485,7 +7480,7 @@ void ShowExampleAppDockSpace(bool* p_open) if (opt_fullscreen) ImGui::PopStyleVar(2); - // DockSpace + // Submit the DockSpace ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { @@ -7519,16 +7514,13 @@ void ShowExampleAppDockSpace(bool* p_open) ImGui::EndMenu(); } HelpMarker( - "When docking is enabled, you can ALWAYS dock MOST window into another! Try it now!" "\n\n" - " > if io.ConfigDockingWithShift==false (default):" "\n" - " drag windows from title bar to dock" "\n" - " > if io.ConfigDockingWithShift==true:" "\n" - " drag windows from anywhere and hold Shift to dock" "\n\n" + "When docking is enabled, you can ALWAYS dock MOST window into another! Try it now!" "\n" + "- Drag from window title bar or their tab to dock/undock." "\n" + "- Drag from window menu button (upper-left button) to undock an entire node (all windows)." "\n" + "- Hold SHIFT to disable docking." "\n" "This demo app has nothing to do with it!" "\n\n" - "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window. This is useful so you can decorate your main application window (e.g. with a menu bar)." "\n\n" - "ImGui::DockSpace() comes with one hard constraint: it needs to be submitted _before_ any window which may be docked into it. Therefore, if you use a dock spot as the central point of your application, you'll probably want it to be part of the very first window you are submitting to imgui every frame." "\n\n" - "(NB: because of this constraint, the implicit \"Debug\" window can not be docked into an explicit DockSpace() node, because that window is submitted as part of the NewFrame() call. An easy workaround is that you can create your own implicit \"Debug##2\" window after calling DockSpace() and leave it in the window stack for anyone to use.)" - ); + "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window." "\n\n" + "Read comments in ShowExampleAppDockSpace() for more details."); ImGui::EndMenuBar(); } @@ -7709,6 +7701,16 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::Separator(); + // About the ImGuiWindowFlags_UnsavedDocument / ImGuiTabItemFlags_UnsavedDocument flags. + // They have multiple effects: + // - Display a dot next to the title. + // - Tab is selected when clicking the X close button. + // - Closure is not assumed (will wait for user to stop submitting the tab). + // Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. + // We need to assume closure by default otherwise waiting for "lack of submission" on the next frame would leave an empty + // hole for one-frame, both in the tab-bar and in tab-contents when closing a tab/window. + // The rarely used SetTabItemClosed() function is a way to notify of programmatic closure to avoid the one-frame hole. + // Tabs if (opt_target == Target_Tab) { diff --git a/examples/interactive/imgui-1.81/imgui_draw.cpp b/examples/interactive/imgui-1.83/imgui_draw.cpp similarity index 91% rename from examples/interactive/imgui-1.81/imgui_draw.cpp rename to examples/interactive/imgui-1.83/imgui_draw.cpp index c217ad8e..8d8c025b 100644 --- a/examples/interactive/imgui-1.81/imgui_draw.cpp +++ b/examples/interactive/imgui-1.83/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (drawing and font code) /* @@ -54,9 +54,12 @@ Index of this file: // Visual Studio warnings #ifdef _MSC_VER -#pragma warning (disable: 4127) // condition expression is constant -#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 6255) // [Static Analyzer] _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead. +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) #endif // Clang/GCC warnings with -Weverything @@ -105,6 +108,9 @@ namespace IMGUI_STB_NAMESPACE #ifdef _MSC_VER #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#pragma warning (disable: 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. +#pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. +#pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. #endif #if defined(__clang__) @@ -145,7 +151,7 @@ namespace IMGUI_STB_NAMESPACE #define STBTT_sqrt(x) ImSqrt(x) #define STBTT_pow(x,y) ImPow(x,y) #define STBTT_fabs(x) ImFabs(x) -#define STBTT_ifloor(x) ((int)ImFloorStd(x)) +#define STBTT_ifloor(x) ((int)ImFloorSigned(x)) #define STBTT_iceil(x) ((int)ImCeil(x)) #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION @@ -380,19 +386,22 @@ ImDrawListSharedData::ImDrawListSharedData() const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } -void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error) +void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) { if (CircleSegmentMaxError == max_error) return; + + IM_ASSERT(max_error > 0.0f); CircleSegmentMaxError = max_error; for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++) { const float radius = (float)i; - const int segment_count = (i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0; - CircleSegmentCounts[i] = (ImU8)ImMin(segment_count, 255); + CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0); } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } // Initialize before use in a new frame. We always have a command ready in the buffer. @@ -488,6 +497,18 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) #define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset #define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset +// Try to merge two last draw commands +void ImDrawList::_TryMergeDrawCmds() +{ + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + ImDrawCmd* prev_cmd = curr_cmd - 1; + if (ImDrawCmd_HeaderCompare(curr_cmd, prev_cmd) == 0 && curr_cmd->UserCallback == NULL && prev_cmd->UserCallback == NULL) + { + prev_cmd->ElemCount += curr_cmd->ElemCount; + CmdBuffer.pop_back(); + } +} + // Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. // The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. void ImDrawList::_OnChangedClipRect() @@ -549,6 +570,16 @@ void ImDrawList::_OnChangedVtxOffset() curr_cmd->VtxOffset = _CmdHeader.VtxOffset; } +int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const +{ + // Automatic segment count + const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy + if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) + return _Data->CircleSegmentCounts[radius_idx]; // Use cached value + else + return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); +} + // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) { @@ -680,17 +711,20 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c } // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. -// Those macros expects l-values. -#define IM_NORMALIZE2F_OVER_ZERO(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = 1.0f / ImSqrt(d2); VX *= inv_len; VY *= inv_len; } } while (0) -#define IM_FIXNORMAL2F(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 < 0.5f) d2 = 0.5f; float inv_lensq = 1.0f / d2; VX *= inv_lensq; VY *= inv_lensq; } while (0) +// - Those macros expects l-values and need to be used as their own statement. +// - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. +#define IM_NORMALIZE2F_OVER_ZERO(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImRsqrt(d2); VX *= inv_len; VY *= inv_len; } } (void)0 +#define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) +#define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 // TODO: Thickness anti-aliased lines cap are missing their AA fringe. // We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness) { if (points_count < 2) return; + const bool closed = (flags & ImDrawFlags_Closed) != 0; const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); @@ -1022,38 +1056,105 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun } } -void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) +void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { - if (radius == 0.0f) + if (radius <= 0.0f) { _Path.push_back(center); return; } - IM_ASSERT(a_min_of_12 <= a_max_of_12); - // For legacy reason the PathArcToFast() always takes angles where 2*PI is represented by 12, - // but it is possible to set IM_DRAWLIST_ARCFAST_TESSELATION_MULTIPLIER to a higher value. This should compile to a no-op otherwise. -#if IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER != 1 - a_min_of_12 *= IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER; - a_max_of_12 *= IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER; -#endif + // Calculate arc auto segment step size + if (a_step <= 0) + a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); + + // Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + + const int sample_range = ImAbs(a_max_sample - a_min_sample); + const int a_next_step = a_step; + + int samples = sample_range + 1; + bool extra_max_sample = false; + if (a_step > 1) + { + samples = sample_range / a_step + 1; + const int overstep = sample_range % a_step; + + if (overstep > 0) + { + extra_max_sample = true; + samples++; + + // When we have overstep to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + if (sample_range > 0) + a_step -= (a_step - overstep) / 2; + } + } + + _Path.resize(_Path.Size + samples); + ImVec2* out_ptr = _Path.Data + (_Path.Size - samples); + + int sample_index = a_min_sample; + if (sample_index < 0 || sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) + { + sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + if (sample_index < 0) + sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + } - _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); - for (int a = a_min_of_12; a <= a_max_of_12; a++) + if (a_max_sample >= a_min_sample) { - const ImVec2& c = _Data->ArcFastVtx[a % IM_ARRAYSIZE(_Data->ArcFastVtx)]; - _Path.push_back(ImVec2(center.x + c.x * radius, center.y + c.y * radius)); + for (int a = a_min_sample; a <= a_max_sample; a += a_step, sample_index += a_step, a_step = a_next_step) + { + // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more + if (sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) + sample_index -= IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[sample_index]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } + } + else + { + for (int a = a_min_sample; a >= a_max_sample; a -= a_step, sample_index -= a_step, a_step = a_next_step) + { + // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[sample_index]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } } + + if (extra_max_sample) + { + int normalized_max_sample = a_max_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + if (normalized_max_sample < 0) + normalized_max_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[normalized_max_sample]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } + + IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr); } -void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) +void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { - if (radius == 0.0f) + if (radius <= 0.0f) { _Path.push_back(center); return; } - IM_ASSERT(a_min <= a_max); // Note that we are adding a point at both a_min and a_max. // If you are trying to draw a full closed circle you don't want the overlapping points! @@ -1065,6 +1166,67 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa } } +// 0: East, 3: South, 6: West, 9: North, 12: East +void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + _PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0); +} + +void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + + if (num_segments > 0) + { + _PathArcToN(center, radius, a_min, a_max, num_segments); + return; + } + + // Automatic segment count + if (radius <= _Data->ArcFastRadiusCutoff) + { + const bool a_is_reverse = a_max < a_min; + + // We are going to use precomputed values for mid samples. + // Determine first and last sample in lookup table that belong to the arc. + const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f); + const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f); + + const int a_min_sample = a_is_reverse ? (int)ImFloorSigned(a_min_sample_f) : (int)ImCeil(a_min_sample_f); + const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloorSigned(a_max_sample_f); + const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0); + + const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const float a_max_segment_angle = a_max_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const bool a_emit_start = (a_min_segment_angle - a_min) != 0.0f; + const bool a_emit_end = (a_max - a_max_segment_angle) != 0.0f; + + _Path.reserve(_Path.Size + (a_mid_samples + 1 + (a_emit_start ? 1 : 0) + (a_emit_end ? 1 : 0))); + if (a_emit_start) + _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)); + if (a_mid_samples > 0) + _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0); + if (a_emit_end) + _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)); + } + else + { + const float arc_length = ImAbs(a_max - a_min); + const int circle_segment_count = _CalcCircleAutoSegmentCount(radius); + const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length)); + _PathArcToN(center, radius, a_min, a_max, arc_segment_count); + } +} + ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t) { float u = 1.0f - t; @@ -1158,12 +1320,47 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, } } -void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawCornerFlags rounding_corners) +IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); +static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) +{ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) + // ~0 --> ImDrawFlags_RoundCornersAll or 0 + if (flags == ~0) + return ImDrawFlags_RoundCornersAll; + + // Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations) + // 0x01 --> ImDrawFlags_RoundCornersTopLeft (VALUE 0x01 OVERLAPS ImDrawFlags_Closed but ImDrawFlags_Closed is never valid in this path!) + // 0x02 --> ImDrawFlags_RoundCornersTopRight + // 0x03 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight + // 0x04 --> ImDrawFlags_RoundCornersBotLeft + // 0x05 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBotLeft + // ... + // 0x0F --> ImDrawFlags_RoundCornersAll or 0 + // (See all values in ImDrawCornerFlags_) + if (flags >= 0x01 && flags <= 0x0F) + return (flags << 4); + + // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' +#endif + + // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. + // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); + + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + + return flags; +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { - rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); - rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + flags = FixRectCornerFlags(flags); + rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f ) - 1.0f); - if (rounding <= 0.0f || rounding_corners == 0) + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PathLineTo(a); PathLineTo(ImVec2(b.x, a.y)); @@ -1172,10 +1369,10 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } else { - const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; - const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; - const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; - const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? rounding : 0.0f; PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); @@ -1189,35 +1386,35 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th return; PathLineTo(p1 + ImVec2(0.5f, 0.5f)); PathLineTo(p2 + ImVec2(0.5f, 0.5f)); - PathStroke(col, false, thickness); + PathStroke(col, 0, thickness); } // p_min = upper-left, p_max = lower-right // Note we don't render 1 pixels sized rectangles properly. -void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners, float thickness) +void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, rounding_corners); + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); else - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, rounding_corners); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, true, thickness); + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + PathStroke(col, ImDrawFlags_Closed, thickness); } -void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners) +void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (rounding > 0.0f) + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - PathRect(p_min, p_max, rounding, rounding_corners); - PathFillConvex(col); + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); } else { - PrimReserve(6, 4); - PrimRect(p_min, p_max, col); + PathRect(p_min, p_max, rounding, flags); + PathFillConvex(col); } } @@ -1246,7 +1443,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); - PathStroke(col, true, thickness); + PathStroke(col, ImDrawFlags_Closed, thickness); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -1269,7 +1466,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); - PathStroke(col, true, thickness); + PathStroke(col, ImDrawFlags_Closed, thickness); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -1292,11 +1489,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if (num_segments <= 0) { // Automatic segment count - const int radius_idx = (int)radius; - if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) - num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value - else - num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); + num_segments = _CalcCircleAutoSegmentCount(radius); } else { @@ -1310,7 +1503,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu PathArcToFast(center, radius - 0.5f, 0, 12 - 1); else PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - PathStroke(col, true, thickness); + PathStroke(col, ImDrawFlags_Closed, thickness); } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) @@ -1322,11 +1515,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, if (num_segments <= 0) { // Automatic segment count - const int radius_idx = (int)radius; - if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) - num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value - else - num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); + num_segments = _CalcCircleAutoSegmentCount(radius); } else { @@ -1352,7 +1541,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - PathStroke(col, true, thickness); + PathStroke(col, ImDrawFlags_Closed, thickness); } // Guaranteed to honor 'num_segments' @@ -1375,7 +1564,7 @@ void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2 PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); - PathStroke(col, false, thickness); + PathStroke(col, 0, thickness); } // Quadratic Bezier takes 3 controls points @@ -1386,7 +1575,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); - PathStroke(col, false, thickness); + PathStroke(col, 0, thickness); } void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) @@ -1455,23 +1644,24 @@ void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, con PopTextureID(); } -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners) +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + flags = FixRectCornerFlags(flags); + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); return; } - const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; if (push_texture_id) PushTextureID(user_texture_id); int vert_start_idx = VtxBuffer.Size; - PathRect(p_min, p_max, rounding, rounding_corners); + PathRect(p_min, p_max, rounding, flags); PathFillConvex(col); int vert_end_idx = VtxBuffer.Size; ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); @@ -1818,6 +2008,7 @@ void ImFontAtlas::ClearInputData() ConfigData.clear(); CustomRects.clear(); PackIdMouseCursors = PackIdLines = -1; + TexReady = false; } void ImFontAtlas::ClearTexData() @@ -1829,14 +2020,15 @@ void ImFontAtlas::ClearTexData() IM_FREE(TexPixelsRGBA32); TexPixelsAlpha8 = NULL; TexPixelsRGBA32 = NULL; + TexPixelsUseColors = false; + // Important: we leave TexReady untouched } void ImFontAtlas::ClearFonts() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (int i = 0; i < Fonts.Size; i++) - IM_DELETE(Fonts[i]); - Fonts.clear(); + Fonts.clear_delete(); + TexReady = false; } void ImFontAtlas::Clear() @@ -1850,11 +2042,7 @@ void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_wid { // Build atlas on demand if (TexPixelsAlpha8 == NULL) - { - if (ConfigData.empty()) - AddFontDefault(); Build(); - } *out_pixels = TexPixelsAlpha8; if (out_width) *out_width = TexWidth; @@ -1913,6 +2101,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; // Invalidate texture + TexReady = false; ClearTexData(); return new_font_cfg.DstFont; } @@ -1984,7 +2173,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float si IM_ASSERT(font_cfg.FontData == NULL); font_cfg.FontData = ttf_data; font_cfg.FontDataSize = ttf_size; - font_cfg.SizePixels = size_pixels; + font_cfg.SizePixels = size_pixels > 0.0f ? size_pixels : font_cfg.SizePixels; if (glyph_ranges) font_cfg.GlyphRanges = glyph_ranges; return AddFont(&font_cfg); @@ -2075,6 +2264,10 @@ bool ImFontAtlas::Build() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + // Default font is none are specified + if (ConfigData.Size == 0) + AddFontDefault(); + // Select builder // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are @@ -2396,9 +2589,8 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) } } - // Cleanup temporary (ImVector doesn't honor destructor) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - src_tmp_array[src_i].~ImFontBuildSrcData(); + // Cleanup + src_tmp_array.clear_destruct(); ImFontAtlasBuildFinish(atlas); return true; @@ -2538,7 +2730,7 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; for (unsigned int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; - + for (unsigned int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = 0xFF; @@ -2550,7 +2742,7 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; for (unsigned int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32_BLACK_TRANS; - + for (unsigned int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = IM_COL32_WHITE; @@ -2614,22 +2806,7 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) if (atlas->Fonts[i]->DirtyLookupTables) atlas->Fonts[i]->BuildLookupTable(); - // Ellipsis character is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Also note that 0x2026 is currently seldom included in our font ranges. Because of this we are more likely to use three individual dots. - for (int i = 0; i < atlas->Fonts.size(); i++) - { - ImFont* font = atlas->Fonts[i]; - if (font->EllipsisChar != (ImWchar)-1) - continue; - const ImWchar ellipsis_variants[] = { (ImWchar)0x2026, (ImWchar)0x0085 }; - for (int j = 0; j < IM_ARRAYSIZE(ellipsis_variants); j++) - if (font->FindGlyphNoFallback(ellipsis_variants[j]) != NULL) // Verify glyph exists - { - font->EllipsisChar = ellipsis_variants[j]; - break; - } - } + atlas->TexReady = true; } // Retrieve list of range (2 int per range, values are inclusive) @@ -2650,6 +2827,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesKorean() 0x0020, 0x00FF, // Basic Latin + Latin Supplement 0x3131, 0x3163, // Korean alphabets 0xAC00, 0xD7A3, // Korean characters + 0xFFFD, 0xFFFD, // Invalid 0, }; return &ranges[0]; @@ -2664,6 +2842,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana 0x31F0, 0x31FF, // Katakana Phonetic Extensions 0xFF00, 0xFFEF, // Half-width characters + 0xFFFD, 0xFFFD, // Invalid 0x4e00, 0x9FAF, // CJK Ideograms 0, }; @@ -2740,7 +2919,8 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() 0x2000, 0x206F, // General Punctuation 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana 0x31F0, 0x31FF, // Katakana Phonetic Extensions - 0xFF00, 0xFFEF // Half-width characters + 0xFF00, 0xFFEF, // Half-width characters + 0xFFFD, 0xFFFD // Invalid }; static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; if (!full_ranges[0]) @@ -2829,7 +3009,8 @@ const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() 0x0020, 0x00FF, // Basic Latin + Latin Supplement 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana 0x31F0, 0x31FF, // Katakana Phonetic Extensions - 0xFF00, 0xFFEF // Half-width characters + 0xFF00, 0xFFEF, // Half-width characters + 0xFFFD, 0xFFFD // Invalid }; static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; if (!full_ranges[0]) @@ -2928,8 +3109,9 @@ ImFont::ImFont() { FontSize = 0.0f; FallbackAdvanceX = 0.0f; - FallbackChar = (ImWchar)'?'; + FallbackChar = (ImWchar)-1; EllipsisChar = (ImWchar)-1; + DotChar = (ImWchar)-1; FallbackGlyph = NULL; ContainerAtlas = NULL; ConfigData = NULL; @@ -2960,6 +3142,14 @@ void ImFont::ClearOutputData() MetricsTotalSurface = 0; } +static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +{ + for (int n = 0; n < candidate_chars_count; n++) + if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) + return candidate_chars[n]; + return (ImWchar)-1; +} + void ImFont::BuildLookupTable() { int max_codepoint = 0; @@ -3002,9 +3192,31 @@ void ImFont::BuildLookupTable() SetGlyphVisible((ImWchar)' ', false); SetGlyphVisible((ImWchar)'\t', false); - // Setup fall-backs + // Ellipsis character is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { (ImWchar)0x2026, (ImWchar)0x0085 }; + const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; + if (EllipsisChar == (ImWchar)-1) + EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); + if (DotChar == (ImWchar)-1) + DotChar = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); + + // Setup fallback character + const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; FallbackGlyph = FindGlyphNoFallback(FallbackChar); - FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + if (FallbackGlyph == NULL) + { + FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); + FallbackGlyph = FindGlyphNoFallback(FallbackChar); + if (FallbackGlyph == NULL) + { + FallbackGlyph = &Glyphs.back(); + FallbackChar = (ImWchar)FallbackGlyph->Codepoint; + } + } + + FallbackAdvanceX = FallbackGlyph->AdvanceX; for (int i = 0; i < max_codepoint + 1; i++) if (IndexAdvanceX[i] < 0.0f) IndexAdvanceX[i] = FallbackAdvanceX; @@ -3029,12 +3241,6 @@ void ImFont::SetGlyphVisible(ImWchar c, bool visible) glyph->Visible = visible ? 1 : 0; } -void ImFont::SetFallbackChar(ImWchar c) -{ - FallbackChar = c; - BuildLookupTable(); -} - void ImFont::GrowIndex(int new_size) { IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); @@ -3319,6 +3525,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons return text_size; } +// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const { const ImFontGlyph* glyph = FindGlyph(c); @@ -3333,6 +3540,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); } +// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const { if (!text_end) @@ -3590,7 +3798,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, false, thickness); + draw_list->PathStroke(col, 0, thickness); } void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) @@ -3604,7 +3812,7 @@ void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, Im if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) { pos -= offset; - const ImTextureID tex_id = font_atlas->TexID; + ImTextureID tex_id = font_atlas->TexID; draw_list->PushTextureID(tex_id); draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); @@ -3631,8 +3839,8 @@ void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half // and because the saved space means that the left-most tab label can stay at exactly the same position as the label of a loose window. void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col) { - draw_list->AddRectFilled(p_min + ImVec2(sz * 0.10f, sz * 0.15f), p_min + ImVec2(sz * 0.70f, sz * 0.30f), col); - RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.40f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); + draw_list->AddRectFilled(p_min + ImVec2(sz * 0.20f, sz * 0.15f), p_min + ImVec2(sz * 0.80f, sz * 0.30f), col); + RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.50f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); } static inline float ImAcos01(float x) @@ -3710,27 +3918,29 @@ void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, ImRect outer, ImRect const bool fill_R = (inner.Max.x < outer.Max.x); const bool fill_U = (inner.Min.y > outer.Min.y); const bool fill_D = (inner.Max.y < outer.Max.y); - if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawCornerFlags_TopLeft) | (fill_D ? 0 : ImDrawCornerFlags_BotLeft)); - if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawCornerFlags_TopRight) | (fill_D ? 0 : ImDrawCornerFlags_BotRight)); - if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, (fill_L ? 0 : ImDrawCornerFlags_TopLeft) | (fill_R ? 0 : ImDrawCornerFlags_TopRight)); - if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, (fill_L ? 0 : ImDrawCornerFlags_BotLeft) | (fill_R ? 0 : ImDrawCornerFlags_BotRight)); - if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawCornerFlags_TopLeft); - if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawCornerFlags_TopRight); - if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawCornerFlags_BotLeft); - if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawCornerFlags_BotRight); + if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft)); + if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight)); + if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft); + if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight); + if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft); + if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); } // Helper for ColorPicker4() // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. // FIXME: uses ImGui::GetColorU32 -void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) { + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags = ImDrawFlags_RoundCornersDefault_; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { - ImU32 col_bg1 = ImGui::GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); - ImU32 col_bg2 = ImGui::GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); - draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); + draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); int yi = 0; for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) @@ -3743,17 +3953,19 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); if (x2 <= x1) continue; - int rounding_corners_flags_cell = 0; - if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } - if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } - rounding_corners_flags_cell &= rounding_corners_flags; - draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; + if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } + + // Combine flags + cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); + draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); } } } else { - draw_list->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + draw_list->AddRectFilled(p_min, p_max, col, rounding, flags); } } diff --git a/examples/interactive/imgui-1.81/imgui_internal.h b/examples/interactive/imgui-1.83/imgui_internal.h similarity index 86% rename from examples/interactive/imgui-1.81/imgui_internal.h rename to examples/interactive/imgui-1.83/imgui_internal.h index de249335..64d08c8c 100644 --- a/examples/interactive/imgui-1.81/imgui_internal.h +++ b/examples/interactive/imgui-1.83/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (internal structures/api) // You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! @@ -43,7 +43,7 @@ Index of this file: //----------------------------------------------------------------------------- #ifndef IMGUI_VERSION -#error Must include imgui.h before imgui_internal.h +#include "imgui.h" #endif #include // FILE*, sscanf @@ -51,10 +51,21 @@ Index of this file: #include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf #include // INT_MIN, INT_MAX +// Enable SSE intrinsics if available +#if (defined __SSE__ || defined __x86_64__ || defined _M_X64) && !defined(IMGUI_DISABLE_SSE) +#define IMGUI_ENABLE_SSE +#include +#endif + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (push) -#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#pragma warning (disable: 26812) // The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif #endif // Clang/GCC warnings with -Weverything @@ -64,6 +75,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloorSigned() #pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h #pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h #pragma clang diagnostic ignored "-Wold-style-cast" @@ -108,9 +120,9 @@ struct ImGuiDockNode; // Docking system node (hold a list of Windo struct ImGuiDockNodeSettings; // Storage for a dock node in .ini file (we preserve those even if the associated dock node isn't active during the session) struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box -struct ImGuiLastItemDataBackup; // Backup and restore IsItemHovered() internal data +struct ImGuiLastItemData; // Status storage for last submitted items struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only -struct ImGuiNavMoveResult; // Result of a gamepad/keyboard directional navigation move query result +struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions @@ -124,16 +136,18 @@ struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) struct ImGuiTable; // Storage for a table struct ImGuiTableColumn; // Storage for one column of a table +struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. struct ImGuiTableSettings; // Storage for a table .ini settings struct ImGuiTableColumnsSettings; // Storage for a column .ini settings struct ImGuiWindow; // Storage for one window -struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame) +struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. typedef int ImGuiDataAuthority; // -> enum ImGuiDataAuthority_ // Enum: for storing the source authority (dock node vs window) of a field typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() +typedef int ImGuiItemAddFlags; // -> enum ImGuiItemAddFlags_ // Flags: for ItemAdd() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() @@ -188,9 +202,9 @@ namespace ImStb // Debug Logging for selected systems. Remove the '((void)0) //' to enable. //#define IMGUI_DEBUG_LOG_POPUP IMGUI_DEBUG_LOG // Enable log +//#define IMGUI_DEBUG_LOG_NAV IMGUI_DEBUG_LOG // Enable log //#define IMGUI_DEBUG_LOG_VIEWPORT IMGUI_DEBUG_LOG // Enable log //#define IMGUI_DEBUG_LOG_DOCKING IMGUI_DEBUG_LOG // Enable log -//#define IMGUI_DEBUG_LOG_NAV IMGUI_DEBUG_LOG // Enable log #define IMGUI_DEBUG_LOG_POPUP(...) ((void)0) // Disable log #define IMGUI_DEBUG_LOG_NAV(...) ((void)0) // Disable log #define IMGUI_DEBUG_LOG_VIEWPORT(...) ((void)0) // Disable log @@ -226,6 +240,7 @@ namespace ImStb #define IM_NEWLINE "\n" #endif #define IM_TABSIZE (4) +#define IM_MEMALIGN(_OFF,_ALIGN) (((_OFF) + (_ALIGN - 1)) & ~(_ALIGN - 1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 #define IM_FLOOR(_VAL) ((float)(int)(_VAL)) // ImFloor() is not inlined in MSVC debug builds @@ -238,6 +253,13 @@ namespace ImStb #define IMGUI_CDECL #endif +// Warnings +#if defined(_MSC_VER) && !defined(__clang__) +#define IM_MSVC_WARNING_SUPPRESS(XXXX) __pragma(warning(suppress: XXXX)) +#else +#define IM_MSVC_WARNING_SUPPRESS(XXXX) +#endif + // Debug Tools // Use 'Metrics->Tools->Item Picker' to break into the call-stack of a specific item. #ifndef IM_DEBUG_BREAK @@ -314,17 +336,19 @@ static inline bool ImCharIsBlankA(char c) { return c == ' ' || c = static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } // Helpers: UTF-8 <> wchar conversions -IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count -IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count -IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count -IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) -IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 -IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 +IMGUI_API const char* ImTextCharToUtf8(char out_buf[5], unsigned int c); // return out_buf +IMGUI_API int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* out_buf, int out_buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 // Helpers: ImVec2/ImVec4 operators // We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.) // We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself. #ifdef IMGUI_DEFINE_MATH_OPERATORS +IM_MSVC_RUNTIME_CHECKS_OFF static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } @@ -340,6 +364,7 @@ static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +IM_MSVC_RUNTIME_CHECKS_RESTORE #endif // Helpers: File System @@ -365,6 +390,7 @@ IMGUI_API ImU64 ImFileWrite(const void* data, ImU64 size, ImU64 coun IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size = NULL, int padding_bytes = 0); // Helpers: Maths +IM_MSVC_RUNTIME_CHECKS_OFF // - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy) #ifndef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS #define ImFabs(X) fabsf(X) @@ -375,16 +401,23 @@ IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* #define ImAcos(X) acosf(X) #define ImAtan2(Y, X) atan2f((Y), (X)) #define ImAtof(STR) atof(STR) -#define ImFloorStd(X) floorf(X) // We already uses our own ImFloor() { return (float)(int)v } internally so the standard one wrapper is named differently (it's used by e.g. stb_truetype) +//#define ImFloorStd(X) floorf(X) // We use our own, see ImFloor() and ImFloorSigned() #define ImCeil(X) ceilf(X) static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision static inline double ImPow(double x, double y) { return pow(x, y); } static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision static inline double ImLog(double x) { return log(x); } +static inline int ImAbs(int x) { return x < 0 ? -x : x; } static inline float ImAbs(float x) { return fabsf(x); } static inline double ImAbs(double x) { return fabs(x); } static inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : ((x > 0.0f) ? 1.0f : 0.0f); } // Sign operator - returns -1, 0 or 1 based on sign of argument static inline double ImSign(double x) { return (x < 0.0) ? -1.0 : ((x > 0.0) ? 1.0 : 0.0); } +#ifdef IMGUI_ENABLE_SSE +static inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +#else +static inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +#endif +static inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double // (Exceptionally using templates here but we could also redefine them for those types) @@ -405,14 +438,16 @@ static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } static inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } static inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } -static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return 1.0f / ImSqrt(d); return fail_value; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } static inline float ImFloor(float f) { return (float)(int)(f); } +static inline float ImFloorSigned(float f) { return (float)((f >= 0 || (int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } static inline int ImModPositive(int a, int b) { return (a + b) % b; } static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry IMGUI_API ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t); @@ -428,6 +463,7 @@ IMGUI_API ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy); // Helper: ImVec1 (1D vector) // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) +IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec1 { float x; @@ -460,6 +496,7 @@ struct IMGUI_API ImRect ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } float GetWidth() const { return Max.x - Min.x; } float GetHeight() const { return Max.y - Min.y; } + float GetArea() const { return (Max.x - Min.x) * (Max.y - Min.y); } ImVec2 GetTL() const { return Min; } // Top-left ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left @@ -480,6 +517,7 @@ struct IMGUI_API ImRect bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } }; +IM_MSVC_RUNTIME_CHECKS_RESTORE // Helper: ImBitArray inline bool ImBitArrayTestBit(const ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; } @@ -499,12 +537,12 @@ inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) // Works on ran } // Helper: ImBitArray class (wrapper over ImBitArray functions) -// Store 1-bit per value. NOT CLEARED by constructor. +// Store 1-bit per value. template struct IMGUI_API ImBitArray { ImU32 Storage[(BITCOUNT + 31) >> 5]; - ImBitArray() { } + ImBitArray() { ClearAllBits(); } void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); } void SetAllBits() { memset(Storage, 255, sizeof(Storage)); } bool TestBit(int n) const { IM_ASSERT(n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } @@ -556,20 +594,22 @@ struct ImSpan // Helper: ImSpanAllocator<> // Facilitate storing multiple chunks into a single large block (the "arena") +// - Usage: call Reserve() N times, allocate GetArenaSizeInBytes() worth, pass it to SetArenaBasePtr(), call GetSpan() N times to retrieve the aligned ranges. template struct ImSpanAllocator { char* BasePtr; - int TotalSize; - int CurrSpan; + int CurrOff; + int CurrIdx; int Offsets[CHUNKS]; + int Sizes[CHUNKS]; ImSpanAllocator() { memset(this, 0, sizeof(*this)); } - inline void ReserveBytes(int n, size_t sz) { IM_ASSERT(n == CurrSpan && n < CHUNKS); IM_UNUSED(n); Offsets[CurrSpan++] = TotalSize; TotalSize += (int)sz; } - inline int GetArenaSizeInBytes() { return TotalSize; } + inline void Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; } + inline int GetArenaSizeInBytes() { return CurrOff; } inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } - inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (void*)(BasePtr + Offsets[n]); } - inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (n + 1 < CHUNKS) ? BasePtr + Offsets[n + 1] : (void*)(BasePtr + TotalSize); } + inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n]); } + inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n] + Sizes[n]); } template inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; @@ -584,26 +624,36 @@ struct IMGUI_API ImPool ImVector Buf; // Contiguous data ImGuiStorage Map; // ID->Index ImPoolIdx FreeIdx; // Next free idx to use + ImPoolIdx AliveCount; // Number of active/alive items (for display purpose) - ImPool() { FreeIdx = 0; } + ImPool() { FreeIdx = AliveCount = 0; } ~ImPool() { Clear(); } T* GetByKey(ImGuiID key) { int idx = Map.GetInt(key, -1); return (idx != -1) ? &Buf[idx] : NULL; } T* GetByIndex(ImPoolIdx n) { return &Buf[n]; } ImPoolIdx GetIndex(const T* p) const { IM_ASSERT(p >= Buf.Data && p < Buf.Data + Buf.Size); return (ImPoolIdx)(p - Buf.Data); } T* GetOrAddByKey(ImGuiID key) { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Buf[*p_idx]; *p_idx = FreeIdx; return Add(); } bool Contains(const T* p) const { return (p >= Buf.Data && p < Buf.Data + Buf.Size); } - void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Buf[idx].~T(); } Map.Clear(); Buf.clear(); FreeIdx = 0; } - T* Add() { int idx = FreeIdx; if (idx == Buf.Size) { Buf.resize(Buf.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Buf[idx]; } IM_PLACEMENT_NEW(&Buf[idx]) T(); return &Buf[idx]; } + void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Buf[idx].~T(); } Map.Clear(); Buf.clear(); FreeIdx = AliveCount = 0; } + T* Add() { int idx = FreeIdx; if (idx == Buf.Size) { Buf.resize(Buf.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Buf[idx]; } IM_PLACEMENT_NEW(&Buf[idx]) T(); AliveCount++; return &Buf[idx]; } void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } - void Remove(ImGuiID key, ImPoolIdx idx) { Buf[idx].~T(); *(int*)&Buf[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); } + void Remove(ImGuiID key, ImPoolIdx idx) { Buf[idx].~T(); *(int*)&Buf[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); AliveCount--; } void Reserve(int capacity) { Buf.reserve(capacity); Map.Data.reserve(capacity); } - int GetSize() const { return Buf.Size; } + + // To iterate a ImPool: for (int n = 0; n < pool.GetMapSize(); n++) if (T* t = pool.TryGetMapData(n)) { ... } + // Can be avoided if you know .Remove() has never been called on the pool, or AliveCount == GetMapSize() + int GetAliveCount() const { return AliveCount; } // Number of active/alive items in the pool (for display purpose) + int GetBufSize() const { return Buf.Size; } + int GetMapSize() const { return Map.Data.Size; } // It is the map we need iterate to find valid items, since we don't have "alive" storage anywhere + T* TryGetMapData(ImPoolIdx n) { int idx = Map.Data[n].val_i; if (idx == -1) return NULL; return GetByIndex(idx); } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + int GetSize() { return GetMapSize(); } // For ImPlot: should use GetMapSize() from (IMGUI_VERSION_NUM >= 18304) +#endif }; // Helper: ImChunkStream<> // Build and iterate a contiguous stream of variable-sized structures. // This is used by Settings to store persistent data while reducing allocation count. -// We store the chunk size first, and align the final size on 4 bytes boundaries (this what the '(X + 3) & ~3' statement is for) +// We store the chunk size first, and align the final size on 4 bytes boundaries. // The tedious/zealous amount of casting is to avoid -Wcast-align warnings. template struct IMGUI_API ImChunkStream @@ -613,7 +663,7 @@ struct IMGUI_API ImChunkStream void clear() { Buf.clear(); } bool empty() const { return Buf.Size == 0; } int size() const { return Buf.Size; } - T* alloc_chunk(size_t sz) { size_t HDR_SZ = 4; sz = ((HDR_SZ + sz) + 3u) & ~3u; int off = Buf.Size; Buf.resize(off + (int)sz); ((int*)(void*)(Buf.Data + off))[0] = (int)sz; return (T*)(void*)(Buf.Data + off + (int)HDR_SZ); } + T* alloc_chunk(size_t sz) { size_t HDR_SZ = 4; sz = IM_MEMALIGN(HDR_SZ + sz, 4u); int off = Buf.Size; Buf.resize(off + (int)sz); ((int*)(void*)(Buf.Data + off))[0] = (int)sz; return (T*)(void*)(Buf.Data + off + (int)HDR_SZ); } T* begin() { size_t HDR_SZ = 4; if (!Buf.Data) return NULL; return (T*)(void*)(Buf.Data + HDR_SZ); } T* next_chunk(T* p) { size_t HDR_SZ = 4; IM_ASSERT(p >= begin() && p < end()); p = (T*)(void*)((char*)(void*)p + chunk_size(p)); if (p == (T*)(void*)((char*)end() + HDR_SZ)) return (T*)0; IM_ASSERT(p < end()); return p; } int chunk_size(const T* p) { return ((const int*)p)[-1]; } @@ -629,15 +679,30 @@ struct IMGUI_API ImChunkStream //----------------------------------------------------------------------------- // ImDrawList: Helper function to calculate a circle's segment count given its radius and a "maximum error" value. -// FIXME: the minimum number of auto-segment may be undesirably high for very small radiuses (e.g. 1.0f) -#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 12 +// Estimation of number of circle segment based on error is derived using method described in https://stackoverflow.com/a/2244088/15194693 +// Number of segments (N) is calculated using equation: +// N = ceil ( pi / acos(1 - error / r) ) where r > 0, error <= r +// Our equation is significantly simpler that one in the post thanks for choosing segment that is +// perpendicular to X axis. Follow steps in the article from this starting condition and you will +// will get this result. +// +// Rendering circles with an odd number of segments, while mathematically correct will produce +// asymmetrical results on the raster grid. Therefore we're rounding N to next even number (7->8, 8->8, 9->10 etc.) +// +#define IM_ROUNDUP_TO_EVEN(_V) ((((_V) + 1) / 2) * 2) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 4 #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX 512 -#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp((int)((IM_PI * 2.0f) / ImAcos(((_RAD) - (_MAXERROR)) / (_RAD))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp(IM_ROUNDUP_TO_EVEN((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD)))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -// ImDrawList: You may set this to higher values (e.g. 2 or 3) to increase tessellation of fast rounded corners path. -#ifndef IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER -#define IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER 1 +// Raw equation from IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC rewritten for 'r' and 'error'. +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI)))) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD)) + +// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle. +#ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE +#define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table. #endif +#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. // Data shared between all ImDrawList instances // You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. @@ -652,12 +717,13 @@ struct IMGUI_API ImDrawListSharedData ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) // [Internal] Lookup tables - ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. + float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); - void SetCircleSegmentMaxError(float max_error); + void SetCircleTessellationMaxError(float max_error); }; struct ImDrawDataBuilder @@ -679,38 +745,58 @@ struct ImDrawDataBuilder enum ImGuiItemFlags_ { ImGuiItemFlags_None = 0, - ImGuiItemFlags_NoTabStop = 1 << 0, // false - ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. - ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211 - ImGuiItemFlags_NoNav = 1 << 3, // false - ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false - ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window - ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) - ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_Default_ = 0 + ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing (FIXME: should merge with _NoNav) + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // Disable interactions but doesn't affect visuals. See PushDisabled()/PushDisabled(). See github.com/ocornut/imgui/issues/211 + ImGuiItemFlags_NoNav = 1 << 3, // false // Disable keyboard/gamepad directional navigation (FIXME: should merge with _NoTabStop) + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false // Disable item being a candidate for default focus (e.g. used by title bar items) + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window + ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) + ImGuiItemFlags_ReadOnly = 1 << 7 // false // [ALPHA] Allow hovering interactions but underlying value is not changed. +}; + +// Flags for ItemAdd() +// FIXME-NAV: _Focusable is _ALMOST_ what you would expect to be called '_TabStop' but because SetKeyboardFocusHere() works on items with no TabStop we distinguish Focusable from TabStop. +enum ImGuiItemAddFlags_ +{ + ImGuiItemAddFlags_None = 0, + ImGuiItemAddFlags_Focusable = 1 << 0 // FIXME-NAV: In current/legacy scheme, Focusable+TabStop support are opt-in by widgets. We will transition it toward being opt-out, so this flag is expected to eventually disappear. }; // Storage for LastItem data enum ImGuiItemStatusFlags_ { ImGuiItemStatusFlags_None = 0, - ImGuiItemStatusFlags_HoveredRect = 1 << 0, - ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, + ImGuiItemStatusFlags_HoveredRect = 1 << 0, // Mouse position is within item rectangle (does NOT mean that the window is in correct z-order and can be hovered!, this is only one part of the most-common IsItemHovered test) + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, // window->DC.LastItemDisplayRect is valid ImGuiItemStatusFlags_Edited = 1 << 2, // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) - ImGuiItemStatusFlags_ToggledSelection = 1 << 3, // Set when Selectable(), TreeNode() reports toggling a selection. We can't report "Selected" because reporting the change allows us to handle clipping with less issues. + ImGuiItemStatusFlags_ToggledSelection = 1 << 3, // Set when Selectable(), TreeNode() reports toggling a selection. We can't report "Selected", only state changes, in order to easily handle clipping with less issues. ImGuiItemStatusFlags_ToggledOpen = 1 << 4, // Set when TreeNode() reports toggling their open state. ImGuiItemStatusFlags_HasDeactivated = 1 << 5, // Set if the widget/group is able to provide data for the ImGuiItemStatusFlags_Deactivated flag. - ImGuiItemStatusFlags_Deactivated = 1 << 6 // Only valid if ImGuiItemStatusFlags_HasDeactivated is set. + ImGuiItemStatusFlags_Deactivated = 1 << 6, // Only valid if ImGuiItemStatusFlags_HasDeactivated is set. + ImGuiItemStatusFlags_HoveredWindow = 1 << 7, // Override the HoveredWindow test to allow cross-window hover testing. + ImGuiItemStatusFlags_FocusedByCode = 1 << 8, // Set when the Focusable item just got focused from code. + ImGuiItemStatusFlags_FocusedByTabbing = 1 << 9, // Set when the Focusable item just got focused by Tabbing. + ImGuiItemStatusFlags_Focused = ImGuiItemStatusFlags_FocusedByCode | ImGuiItemStatusFlags_FocusedByTabbing #ifdef IMGUI_ENABLE_TEST_ENGINE , // [imgui_tests only] - ImGuiItemStatusFlags_Openable = 1 << 10, // - ImGuiItemStatusFlags_Opened = 1 << 11, // - ImGuiItemStatusFlags_Checkable = 1 << 12, // - ImGuiItemStatusFlags_Checked = 1 << 13 // + ImGuiItemStatusFlags_Openable = 1 << 20, // + ImGuiItemStatusFlags_Opened = 1 << 21, // + ImGuiItemStatusFlags_Checkable = 1 << 22, // + ImGuiItemStatusFlags_Checked = 1 << 23 // #endif }; +// Extend ImGuiInputTextFlags_ +enum ImGuiInputTextFlagsPrivate_ +{ + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() + ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data + ImGuiInputTextFlags_MergedItem = 1 << 28 // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. +}; + // Extend ImGuiButtonFlags_ enum ImGuiButtonFlagsPrivate_ { @@ -724,7 +810,7 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping ImGuiButtonFlags_AllowItemOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press // [UNUSED] - ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions + //ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use PushDisabled() or ImGuiItemFlags_Disabled ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) @@ -734,6 +820,12 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease }; +// Extend ImGuiComboFlags_ +enum ImGuiComboFlagsPrivate_ +{ + ImGuiComboFlags_CustomPreview = 1 << 20 // enable BeginComboPreview() +}; + // Extend ImGuiSliderFlags_ enum ImGuiSliderFlagsPrivate_ { @@ -746,12 +838,13 @@ enum ImGuiSelectableFlagsPrivate_ { // NB: need to be in sync with last value of ImGuiSelectableFlags_ ImGuiSelectableFlags_NoHoldingActiveID = 1 << 20, - ImGuiSelectableFlags_SelectOnClick = 1 << 21, // Override button behavior to react on Click (default is Click+Release) - ImGuiSelectableFlags_SelectOnRelease = 1 << 22, // Override button behavior to react on Release (default is Click+Release) - ImGuiSelectableFlags_SpanAvailWidth = 1 << 23, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) - ImGuiSelectableFlags_DrawHoveredWhenHeld = 1 << 24, // Always show active when held, even is not hovered. This concept could probably be renamed/formalized somehow. - ImGuiSelectableFlags_SetNavIdOnHover = 1 << 25, // Set Nav/Focus ID on mouse hover (used by MenuItem) - ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 26 // Disable padding each side with ItemSpacing * 0.5f + ImGuiSelectableFlags_SelectOnNav = 1 << 21, // (WIP) Auto-select when moved into. This is not exposed in public API as to handle multi-select and modifiers we will need user to explicitly control focus scope. May be replaced with a BeginSelection() API. + ImGuiSelectableFlags_SelectOnClick = 1 << 22, // Override button behavior to react on Click (default is Click+Release) + ImGuiSelectableFlags_SelectOnRelease = 1 << 23, // Override button behavior to react on Release (default is Click+Release) + ImGuiSelectableFlags_SpanAvailWidth = 1 << 24, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) + ImGuiSelectableFlags_DrawHoveredWhenHeld = 1 << 25, // Always show active when held, even is not hovered. This concept could probably be renamed/formalized somehow. + ImGuiSelectableFlags_SetNavIdOnHover = 1 << 26, // Set Nav/Focus ID on mouse hover (used by MenuItem) + ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 27 // Disable padding each side with ItemSpacing * 0.5f }; // Extend ImGuiTreeNodeFlags_ @@ -815,9 +908,10 @@ enum ImGuiInputSource { ImGuiInputSource_None = 0, ImGuiInputSource_Mouse, - ImGuiInputSource_Nav, - ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code - ImGuiInputSource_NavGamepad, // " + ImGuiInputSource_Keyboard, + ImGuiInputSource_Gamepad, + ImGuiInputSource_Nav, // Stored in g.ActiveIdSource only + ImGuiInputSource_Clipboard, // Currently only used by InputText() ImGuiInputSource_COUNT }; @@ -921,8 +1015,21 @@ struct ImGuiStyleMod ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } }; +// Storage data for BeginComboPreview()/EndComboPreview() +struct IMGUI_API ImGuiComboPreviewData +{ + ImRect PreviewRect; + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + ImVec2 BackupCursorPosPrevLine; + float BackupPrevLineTextBaseOffset; + ImGuiLayoutType BackupLayout; + + ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); } +}; + // Stacked storage data for BeginGroup()/EndGroup() -struct ImGuiGroupData +struct IMGUI_API ImGuiGroupData { ImGuiID WindowID; ImVec2 BackupCursorPos; @@ -933,20 +1040,26 @@ struct ImGuiGroupData float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; bool BackupActiveIdPreviousFrameIsAlive; + bool BackupHoveredIdIsAlive; bool EmitItem; }; // Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper. struct IMGUI_API ImGuiMenuColumns { - float Spacing; - float Width, NextWidth; - float Pos[3], NextWidths[3]; + ImU32 TotalWidth; + ImU32 NextTotalWidth; + ImU16 Spacing; + ImU16 OffsetIcon; // Always zero for now + ImU16 OffsetLabel; // Offsets are locked in Update() + ImU16 OffsetShortcut; + ImU16 OffsetMark; + ImU16 Widths[4]; // Width of: Icon, Label, Shortcut, Mark (accumulators for current frame) ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } - void Update(int count, float spacing, bool clear); - float DeclColumns(float w0, float w1, float w2); - float CalcExtraSpace(float avail_w) const; + void Update(float spacing, bool window_reappearing); + float DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark); + void CalcNextTotalWidth(bool update_offsets); }; // Internal state of the currently focused/edited text input box @@ -966,7 +1079,7 @@ struct IMGUI_API ImGuiInputTextState bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame - ImGuiInputTextFlags UserFlags; // Temporarily set while we call user's callback + ImGuiInputTextFlags Flags; // copy of InputText() flags ImGuiInputTextCallback UserCallback; // " void* UserCallbackData; // " @@ -982,6 +1095,9 @@ struct IMGUI_API ImGuiInputTextState void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } bool HasSelection() const { return Stb.select_start != Stb.select_end; } void ClearSelection() { Stb.select_start = Stb.select_end = Stb.cursor; } + int GetCursorPos() const { return Stb.cursor; } + int GetSelectionStart() const { return Stb.select_start; } + int GetSelectionEnd() const { return Stb.select_end; } void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } }; @@ -999,18 +1115,18 @@ struct ImGuiPopupData ImGuiPopupData() { memset(this, 0, sizeof(*this)); OpenFrameCount = -1; } }; -struct ImGuiNavMoveResult +struct ImGuiNavItemData { - ImGuiWindow* Window; // Best candidate window - ImGuiID ID; // Best candidate ID - ImGuiID FocusScopeId; // Best candidate focus scope ID - float DistBox; // Best candidate box distance to current NavId - float DistCenter; // Best candidate center distance to current NavId - float DistAxial; - ImRect RectRel; // Best candidate bounding box in window relative space + ImGuiWindow* Window; // Init,Move // Best candidate window (result->ItemWindow->RootWindowForNav == request->Window) + ImGuiID ID; // Init,Move // Best candidate item ID + ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID + ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space + float DistBox; // Move // Best candidate box distance to current NavId + float DistCenter; // Move // Best candidate center distance to current NavId + float DistAxial; // Move // Best candidate axial distance to current NavId - ImGuiNavMoveResult() { Clear(); } - void Clear() { Window = NULL; ID = FocusScopeId = 0; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } + ImGuiNavItemData() { Clear(); } + void Clear() { Window = NULL; ID = FocusScopeId = 0; RectRel = ImRect(); DistBox = DistCenter = DistAxial = FLT_MAX; } }; enum ImGuiNextWindowDataFlags_ @@ -1076,6 +1192,25 @@ struct ImGuiNextItemData inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } // Also cleared manually by ItemAdd()! }; +// Status storage for the last submitted item +struct ImGuiLastItemData +{ + ImGuiID ID; + ImGuiItemFlags InFlags; // See ImGuiItemFlags_ + ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ + ImRect Rect; + ImRect DisplayRect; + + ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } +}; + +// Data saved for each window pushed into the stack +struct ImGuiWindowStackData +{ + ImGuiWindow* Window; + ImGuiLastItemData ParentLastItemDataBackup; +}; + struct ImGuiShrinkWidthItem { int Index; @@ -1175,9 +1310,10 @@ enum ImGuiDockNodeFlagsPrivate_ ImGuiDockNodeFlags_NoDockingSplitMe = 1 << 17, // [EXPERIMENTAL] Prevent another window/node from splitting this node. ImGuiDockNodeFlags_NoDockingSplitOther = 1 << 18, // [EXPERIMENTAL] Prevent this node from splitting another window/node. ImGuiDockNodeFlags_NoDockingOverMe = 1 << 19, // [EXPERIMENTAL] Prevent another window/node to be docked over this node. - ImGuiDockNodeFlags_NoDockingOverOther = 1 << 20, // [EXPERIMENTAL] Prevent this node to be docked over another window/node. - ImGuiDockNodeFlags_NoResizeX = 1 << 21, // [EXPERIMENTAL] - ImGuiDockNodeFlags_NoResizeY = 1 << 22, // [EXPERIMENTAL] + ImGuiDockNodeFlags_NoDockingOverOther = 1 << 20, // [EXPERIMENTAL] Prevent this node to be docked over another window or non-empty node. + ImGuiDockNodeFlags_NoDockingOverEmpty = 1 << 21, // [EXPERIMENTAL] Prevent this node to be docked over an empty node (e.g. DockSpace with no other windows) + ImGuiDockNodeFlags_NoResizeX = 1 << 22, // [EXPERIMENTAL] + ImGuiDockNodeFlags_NoResizeY = 1 << 23, // [EXPERIMENTAL] ImGuiDockNodeFlags_SharedFlagsInheritMask_ = ~0, ImGuiDockNodeFlags_NoResizeFlagsMask_ = ImGuiDockNodeFlags_NoResize | ImGuiDockNodeFlags_NoResizeX | ImGuiDockNodeFlags_NoResizeY, ImGuiDockNodeFlags_LocalFlagsMask_ = ImGuiDockNodeFlags_NoSplit | ImGuiDockNodeFlags_NoResizeFlagsMask_ | ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton | ImGuiDockNodeFlags_NoDocking, @@ -1201,12 +1337,14 @@ enum ImGuiDockNodeState ImGuiDockNodeState_HostWindowVisible }; -// sizeof() 116~160 -struct ImGuiDockNode +// sizeof() 156~192 +struct IMGUI_API ImGuiDockNode { ImGuiID ID; - ImGuiDockNodeFlags SharedFlags; // Flags shared by all nodes of a same dockspace hierarchy (inherited from the root node) - ImGuiDockNodeFlags LocalFlags; // Flags specific to this node + ImGuiDockNodeFlags SharedFlags; // (Write) Flags shared by all nodes of a same dockspace hierarchy (inherited from the root node) + ImGuiDockNodeFlags LocalFlags; // (Write) Flags specific to this node + ImGuiDockNodeFlags LocalFlagsInWindows; // (Write) Flags specific to this node, applied from windows + ImGuiDockNodeFlags MergedFlags; // (Read) Effective flags (== SharedFlags | LocalFlagsInNode | LocalFlagsInWindows) ImGuiDockNodeState State; ImGuiDockNode* ParentNode; ImGuiDockNode* ChildNodes[2]; // [Split node only] Child nodes (left/right or top/bottom). Consider switching to an array. @@ -1233,9 +1371,8 @@ struct ImGuiDockNode ImGuiDataAuthority AuthorityForViewport :3; bool IsVisible :1; // Set to false when the node is hidden (usually disabled as it has no active window) bool IsFocused :1; - bool HasCloseButton :1; + bool HasCloseButton :1; // Provide space for a close button (if any of the docked window has one). Note that button may be hidden on window without one. bool HasWindowMenuButton :1; - bool EnableCloseButton :1; bool WantCloseAll :1; // Set when closing all tabs at once. bool WantLockSizeOnce :1; bool WantMouseMove :1; // After a node extraction we need to transition toward moving the newly created host window @@ -1246,16 +1383,18 @@ struct ImGuiDockNode ImGuiDockNode(ImGuiID id); ~ImGuiDockNode(); bool IsRootNode() const { return ParentNode == NULL; } - bool IsDockSpace() const { return (LocalFlags & ImGuiDockNodeFlags_DockSpace) != 0; } - bool IsFloatingNode() const { return ParentNode == NULL && (LocalFlags & ImGuiDockNodeFlags_DockSpace) == 0; } - bool IsCentralNode() const { return (LocalFlags & ImGuiDockNodeFlags_CentralNode) != 0; } - bool IsHiddenTabBar() const { return (LocalFlags & ImGuiDockNodeFlags_HiddenTabBar) != 0; } // Hidden tab bar can be shown back by clicking the small triangle - bool IsNoTabBar() const { return (LocalFlags & ImGuiDockNodeFlags_NoTabBar) != 0; } // Never show a tab bar + bool IsDockSpace() const { return (MergedFlags & ImGuiDockNodeFlags_DockSpace) != 0; } + bool IsFloatingNode() const { return ParentNode == NULL && (MergedFlags & ImGuiDockNodeFlags_DockSpace) == 0; } + bool IsCentralNode() const { return (MergedFlags & ImGuiDockNodeFlags_CentralNode) != 0; } + bool IsHiddenTabBar() const { return (MergedFlags & ImGuiDockNodeFlags_HiddenTabBar) != 0; } // Hidden tab bar can be shown back by clicking the small triangle + bool IsNoTabBar() const { return (MergedFlags & ImGuiDockNodeFlags_NoTabBar) != 0; } // Never show a tab bar bool IsSplitNode() const { return ChildNodes[0] != NULL; } bool IsLeafNode() const { return ChildNodes[0] == NULL; } bool IsEmpty() const { return ChildNodes[0] == NULL && Windows.Size == 0; } - ImGuiDockNodeFlags GetMergedFlags() const { return SharedFlags | LocalFlags; } ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + + void SetLocalFlags(ImGuiDockNodeFlags flags) { LocalFlags = flags; UpdateMergedFlags(); } + void UpdateMergedFlags() { MergedFlags = SharedFlags | LocalFlags | LocalFlagsInWindows; } }; // List of colors that are stored at the time of Begin() into Docked Windows. @@ -1316,15 +1455,22 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 LastRendererSize; ImVec2 WorkOffsetMin; // Work Area: Offset from Pos to top-left corner of Work Area. Generally (0,0) or (0,+main_menu_bar_height). Work Area is Full Area but without menu-bars/status-bars (so WorkArea always fit inside Pos/Size!) ImVec2 WorkOffsetMax; // Work Area: Offset from Pos+Size to bottom-right corner of Work Area. Generally (0,0) or (0,-status_bar_height). - ImVec2 CurrWorkOffsetMin; // Work Area: Offset being built/increased during current frame - ImVec2 CurrWorkOffsetMax; // Work Area: Offset being built/decreased during current frame + ImVec2 BuildWorkOffsetMin; // Work Area: Offset being built during current frame. Generally >= 0.0f. + ImVec2 BuildWorkOffsetMax; // Work Area: Offset being built during current frame. Generally <= 0.0f. + + ImGuiViewportP() { Idx = -1; LastFrameActive = DrawListsLastFrame[0] = DrawListsLastFrame[1] = LastFrontMostStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; PlatformMonitor = -1; PlatformWindowCreated = false; Window = NULL; DrawLists[0] = DrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); } + ~ImGuiViewportP() { if (DrawLists[0]) IM_DELETE(DrawLists[0]); if (DrawLists[1]) IM_DELETE(DrawLists[1]); } + void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; } + + // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) + ImVec2 CalcWorkRectPos(const ImVec2& off_min) const { return ImVec2(Pos.x + off_min.x, Pos.y + off_min.y); } + ImVec2 CalcWorkRectSize(const ImVec2& off_min, const ImVec2& off_max) const { return ImVec2(ImMax(0.0f, Size.x - off_min.x + off_max.x), ImMax(0.0f, Size.y - off_min.y + off_max.y)); } + void UpdateWorkRect() { WorkPos = CalcWorkRectPos(WorkOffsetMin); WorkSize = CalcWorkRectSize(WorkOffsetMin, WorkOffsetMax); } // Update public fields - ImGuiViewportP() { Idx = -1; LastFrameActive = DrawListsLastFrame[0] = DrawListsLastFrame[1] = LastFrontMostStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; PlatformMonitor = -1; PlatformWindowCreated = false; Window = NULL; DrawLists[0] = DrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); } - ~ImGuiViewportP() { if (DrawLists[0]) IM_DELETE(DrawLists[0]); if (DrawLists[1]) IM_DELETE(DrawLists[1]); } - ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - ImRect GetWorkRect() const { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); } - void UpdateWorkRect() { WorkPos = ImVec2(Pos.x + WorkOffsetMin.x, Pos.y + WorkOffsetMin.y); WorkSize = ImVec2(ImMax(0.0f, Size.x - WorkOffsetMin.x + WorkOffsetMax.x), ImMax(0.0f, Size.y - WorkOffsetMin.y + WorkOffsetMax.y)); } - void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; } + // Helpers to retrieve ImRect (we don't need to store BuildWorkRect as every access tend to change it, hence the code asymmetry) + ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + ImRect GetWorkRect() const { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); } + ImRect GetBuildWorkRect() const { ImVec2 pos = CalcWorkRectPos(BuildWorkOffsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkOffsetMin, BuildWorkOffsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } }; //----------------------------------------------------------------------------- @@ -1459,17 +1605,17 @@ struct ImGuiContext // Windows state ImVector Windows; // Windows, sorted in display order, back to front - ImVector WindowsFocusOrder; // Windows, sorted in focus order, back to front. (FIXME: We could only store root windows here! Need to sort out the Docking equivalent which is RootWindowDockStop and is unfortunately a little more dynamic) + ImVector WindowsFocusOrder; // Root windows, sorted in focus order, back to front. ImVector WindowsTempSortBuffer; // Temporary buffer used in EndFrame() to reorder windows so parents are kept before their child - ImVector CurrentWindowStack; + ImVector CurrentWindowStack; ImGuiStorage WindowsById; // Map window's ImGuiID to ImGuiWindow* int WindowsActiveCount; // Number of unique windows submitted by frame + ImVec2 WindowsHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, WINDOWS_HOVER_PADDING) ImGuiWindow* CurrentWindow; // Window being drawn into ImGuiWindow* HoveredWindow; // Window the mouse is hovering. Will typically catch mouse inputs. - ImGuiWindow* HoveredRootWindow; // == HoveredWindow ? HoveredWindow->RootWindow : NULL, merely a shortcut to avoid null test in some situation. ImGuiWindow* HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set. ImGuiDockNode* HoveredDockNode; // Hovered dock node. - ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindowDockTree. ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window. ImVec2 WheelingWindowRefMousePos; float WheelingWindowTimer; @@ -1508,8 +1654,10 @@ struct ImGuiContext float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. // Next window/item data - ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions + ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions // Shared stacks ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() @@ -1528,6 +1676,7 @@ struct ImGuiContext ImGuiViewportP* MouseViewport; ImGuiViewportP* MouseLastHoveredViewport; // Last known viewport that was hovered by mouse (even if we are not hovering any viewport any more) + honoring the _NoInputs flag. ImGuiID PlatformLastFocusedViewportId; + ImGuiPlatformMonitor FallbackMonitor; // Virtual monitor used as fallback if backend doesn't provide monitor information. int ViewportFrontMostStampCount; // Every time the front-most window changes, we stamp its viewport with an incrementing counter // Gamepad/keyboard Navigation @@ -1563,9 +1712,9 @@ struct ImGuiContext ImGuiKeyModFlags NavMoveRequestKeyMods; ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? - ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow - ImGuiNavMoveResult NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) - ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) + ImGuiNavItemData NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavItemData NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) + ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) ImGuiWindow* NavWrapRequestWindow; // Window which requested trying nav wrap-around. ImGuiNavMoveFlags NavWrapRequestFlags; // Wrap-around operation flags. @@ -1578,13 +1727,13 @@ struct ImGuiContext bool NavWindowingToggleLayer; // Legacy Focus/Tabbing system (older than Nav, active even if Nav is disabled, misnamed. FIXME-NAV: This needs a redesign!) - ImGuiWindow* FocusRequestCurrWindow; // - ImGuiWindow* FocusRequestNextWindow; // - int FocusRequestCurrCounterRegular; // Any item being requested for focus, stored as an index (we on layout to be stable between the frame pressing TAB and the next frame, semi-ouch) - int FocusRequestCurrCounterTabStop; // Tab item being requested for focus, stored as an index - int FocusRequestNextCounterRegular; // Stored for next frame - int FocusRequestNextCounterTabStop; // " - bool FocusTabPressed; // + ImGuiWindow* TabFocusRequestCurrWindow; // + ImGuiWindow* TabFocusRequestNextWindow; // + int TabFocusRequestCurrCounterRegular; // Any item being requested for focus, stored as an index (we on layout to be stable between the frame pressing TAB and the next frame, semi-ouch) + int TabFocusRequestCurrCounterTabStop; // Tab item being requested for focus, stored as an index + int TabFocusRequestNextCounterRegular; // Stored for next frame + int TabFocusRequestNextCounterTabStop; // " + bool TabFocusPressed; // Set in NewFrame() when user pressed Tab // Render float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) @@ -1611,8 +1760,9 @@ struct ImGuiContext // Table ImGuiTable* CurrentTable; + int CurrentTableStackIdx; ImPool Tables; - ImVector CurrentTableStack; + ImVector TablesTempDataStack; ImVector TablesLastTimeActive; // Last used timestamp of each tables (SOA, for efficient GC) ImVector DrawChannelsTempMergeBuffer; @@ -1632,6 +1782,7 @@ struct ImGuiContext float ColorEditLastSat; // Backup of last Saturation associated to LastColor[3], so we can restore Saturation in lossy RGB<>HSV round trips float ColorEditLastColor[3]; ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker. + ImGuiComboPreviewData ComboPreviewData; float SliderCurrentAccum; // Accumulated slider delta when using navigation controls. bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it? bool DragCurrentAccumDirty; @@ -1684,6 +1835,7 @@ struct ImGuiContext // Misc float FramerateSecPerFrame[120]; // Calculate estimate of framerate for user over the last 2 seconds. int FramerateSecPerFrameIdx; + int FramerateSecPerFrameCount; float FramerateSecPerFrameAccum; int WantCaptureMouseNextFrame; // Explicit capture via CaptureKeyboardFromApp()/CaptureMouseFromApp() sets those flags int WantCaptureKeyboardNextFrame; @@ -1710,7 +1862,6 @@ struct ImGuiContext WindowsActiveCount = 0; CurrentWindow = NULL; HoveredWindow = NULL; - HoveredRootWindow = NULL; HoveredWindowUnderMovingWindow = NULL; HoveredDockNode = NULL; MovingWindow = NULL; @@ -1738,7 +1889,7 @@ struct ImGuiContext ActiveIdClickOffset = ImVec2(-1, -1); ActiveIdWindow = NULL; ActiveIdSource = ImGuiInputSource_None; - ActiveIdMouseButton = 0; + ActiveIdMouseButton = -1; ActiveIdPreviousFrame = 0; ActiveIdPreviousFrameIsAlive = false; ActiveIdPreviousFrameHasBeenEditedBefore = false; @@ -1746,6 +1897,8 @@ struct ImGuiContext LastActiveId = 0; LastActiveIdTimer = 0.0f; + CurrentItemFlags = ImGuiItemFlags_None; + CurrentDpiScale = 0.0f; CurrentViewport = NULL; MouseViewport = MouseLastHoveredViewport = NULL; @@ -1781,10 +1934,10 @@ struct ImGuiContext NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; - FocusRequestCurrWindow = FocusRequestNextWindow = NULL; - FocusRequestCurrCounterRegular = FocusRequestCurrCounterTabStop = INT_MAX; - FocusRequestNextCounterRegular = FocusRequestNextCounterTabStop = INT_MAX; - FocusTabPressed = false; + TabFocusRequestCurrWindow = TabFocusRequestNextWindow = NULL; + TabFocusRequestCurrCounterRegular = TabFocusRequestCurrCounterTabStop = INT_MAX; + TabFocusRequestNextCounterRegular = TabFocusRequestNextCounterTabStop = INT_MAX; + TabFocusPressed = false; DimBgRatio = 0.0f; MouseCursor = ImGuiMouseCursor_Arrow; @@ -1802,11 +1955,12 @@ struct ImGuiContext memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); CurrentTable = NULL; + CurrentTableStackIdx = -1; CurrentTabBar = NULL; LastValidMousePos = ImVec2(0.0f, 0.0f); TempInputId = 0; - ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditLastHue = ColorEditLastSat = 0.0f; ColorEditLastColor[0] = ColorEditLastColor[1] = ColorEditLastColor[2] = FLT_MAX; SliderCurrentAccum = 0.0f; @@ -1839,7 +1993,7 @@ struct ImGuiContext DebugItemPickerBreakId = 0; memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); - FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; FramerateSecPerFrameAccum = 0.0f; WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; memset(TempBuffer, 0, sizeof(TempBuffer)); @@ -1869,16 +2023,10 @@ struct IMGUI_API ImGuiWindowTempData ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. ImVec1 GroupOffset; - // Last item status - ImGuiID LastItemId; // ID for last item - ImGuiItemStatusFlags LastItemStatusFlags; // Status flags for last item (see ImGuiItemStatusFlags_) - ImRect LastItemRect; // Interaction rect for last item - ImRect LastItemDisplayRect; // End-user display rect for last item (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) - // Keyboard/Gamepad navigation ImGuiNavLayer NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) - int NavLayerActiveMask; // Which layers have been written to (result from previous frame) - int NavLayerActiveMaskNext; // Which layers have been written to (accumulator for current frame) + short NavLayersActiveMask; // Which layers have been written to (result from previous frame) + short NavLayersActiveMaskNext;// Which layers have been written to (accumulator for current frame) ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending bool NavHideHighlightOneFrame; bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) @@ -1900,7 +2048,6 @@ struct IMGUI_API ImGuiWindowTempData // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. - ImGuiItemFlags ItemFlags; // == g.ItemFlagsStack.back() float ItemWidth; // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window). float TextWrapPos; // Current text wrap pos. ImVector ItemWidthStack; // Store item widths to restore (attention: .back() is not == ItemWidth) @@ -1951,8 +2098,9 @@ struct IMGUI_API ImGuiWindow bool HasCloseButton; // Set when the window has a close button (p_open != NULL) signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) short BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) - short BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. - short BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. + short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. + short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) ImS8 AutoFitFramesX, AutoFitFramesY; ImS8 AutoFitChildAxises; @@ -1961,6 +2109,7 @@ struct IMGUI_API ImGuiWindow ImS8 HiddenFramesCanSkipItems; // Hide the window for N frames ImS8 HiddenFramesCannotSkipItems; // Hide the window for N frames while allowing items to be submitted so we can measure their size ImS8 HiddenFramesForRenderOnly; // Hide the window until frame N at Render() time only + ImS8 DisableInputsFrames; // Disable window interactions for N frames ImGuiCond SetWindowPosAllowFlags : 8; // store acceptable condition flags for SetNextWindowPos() use. ImGuiCond SetWindowSizeAllowFlags : 8; // store acceptable condition flags for SetNextWindowSize() use. ImGuiCond SetWindowCollapsedAllowFlags : 8; // store acceptable condition flags for SetNextWindowCollapsed() use. @@ -1996,8 +2145,8 @@ struct IMGUI_API ImGuiWindow ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) ImDrawList DrawListInst; ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. - ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window == Top-level window. - ImGuiWindow* RootWindowDockStop; // Point to ourself or first ancestor that is not a child window. Doesn't cross through dock nodes. We use this so IsWindowFocused() can behave consistently regardless of docking state. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. Doesn't cross through dock nodes. We use this so IsWindowFocused() can behave consistently regardless of docking state. + ImGuiWindow* RootWindowDockTree; // Point to ourself or first ancestor that is not a child window. Cross through dock nodes. ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. @@ -2011,6 +2160,7 @@ struct IMGUI_API ImGuiWindow // Docking bool DockIsActive :1; // When docking artifacts are actually visible. When this is set, DockNode is guaranteed to be != NULL. ~~ (DockNode != NULL) && (DockNode->Windows.Size > 1). + bool DockNodeIsVisible :1; bool DockTabIsVisible :1; // Is our window visible this frame? ~~ is the corresponding tab selected? bool DockTabWantClose :1; short DockOrder; // Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible. @@ -2042,19 +2192,6 @@ struct IMGUI_API ImGuiWindow ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } }; -// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. -struct ImGuiLastItemDataBackup -{ - ImGuiID LastItemId; - ImGuiItemStatusFlags LastItemStatusFlags; - ImRect LastItemRect; - ImRect LastItemDisplayRect; - - ImGuiLastItemDataBackup() { Backup(); } - void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } - void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } -}; - //----------------------------------------------------------------------------- // [SECTION] Tab bar, Tab item support //----------------------------------------------------------------------------- @@ -2070,13 +2207,14 @@ enum ImGuiTabBarFlagsPrivate_ // Extend ImGuiTabItemFlags_ enum ImGuiTabItemFlagsPrivate_ { + ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) ImGuiTabItemFlags_Button = 1 << 21, // Used by TabItemButton, change the tab item behavior to mimic a button ImGuiTabItemFlags_Unsorted = 1 << 22, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. ImGuiTabItemFlags_Preview = 1 << 23 // [Docking] Display tab shape for docking preview (height is adjusted slightly to compensate for the yet missing tab bar) }; -// Storage for one active tab item (sizeof() 32~40 bytes) +// Storage for one active tab item (sizeof() 48 bytes) struct ImGuiTabItem { ImGuiID ID; @@ -2087,22 +2225,22 @@ struct ImGuiTabItem float Offset; // Position relative to beginning of tab float Width; // Width currently displayed float ContentWidth; // Width of label, stored during BeginTabItem() call - ImS16 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames + ImS32 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable ImS16 IndexDuringLayout; // Index only used during TabBarLayout() bool WantClose; // Marked as closed by SetTabItemClosed() - ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; NameOffset = BeginOrder = IndexDuringLayout = -1; } + ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } }; // Storage for a tab bar (sizeof() 152 bytes) -struct ImGuiTabBar +struct IMGUI_API ImGuiTabBar { ImVector Tabs; ImGuiTabBarFlags Flags; ImGuiID ID; // Zero for tab-bars used by docking ImGuiID SelectedTabId; // Selected tab/window - ImGuiID NextSelectedTabId; + ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview) int CurrFrameVisible; int PrevFrameVisible; @@ -2118,7 +2256,7 @@ struct ImGuiTabBar float ScrollingRectMinX; float ScrollingRectMaxX; ImGuiID ReorderRequestTabId; - ImS8 ReorderRequestDir; + ImS16 ReorderRequestOffset; ImS8 BeginCount; bool WantLayout; bool VisibleTabWasSubmitted; @@ -2136,7 +2274,7 @@ struct ImGuiTabBar { if (tab->Window) return tab->Window->Name; - IM_ASSERT(tab->NameOffset != -1 && (int)tab->NameOffset < TabsNames.Buf.Size); + IM_ASSERT(tab->NameOffset != -1 && tab->NameOffset < TabsNames.Buf.Size); return TabsNames.Buf.Data + tab->NameOffset; } }; @@ -2145,8 +2283,6 @@ struct ImGuiTabBar // [SECTION] Table support //----------------------------------------------------------------------------- -#ifdef IMGUI_HAS_TABLE - #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. #define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. #define IMGUI_TABLE_MAX_DRAW_CHANNELS (4 + 64 * 2) // See TableSetupDrawChannels() @@ -2185,10 +2321,11 @@ struct ImGuiTableColumn ImGuiTableColumnIdx NextEnabledColumn; // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column ImGuiTableColumnIdx SortOrder; // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort ImGuiTableDrawChannelIdx DrawChannelCurrent; // Index within DrawSplitter.Channels[] - ImGuiTableDrawChannelIdx DrawChannelFrozen; - ImGuiTableDrawChannelIdx DrawChannelUnfrozen; - bool IsEnabled; // Is the column not marked Hidden by the user? (even if off view, e.g. clipped by scrolling). - bool IsEnabledNextFrame; + ImGuiTableDrawChannelIdx DrawChannelFrozen; // Draw channels for frozen rows (often headers) + ImGuiTableDrawChannelIdx DrawChannelUnfrozen; // Draw channels for unfrozen rows + bool IsEnabled; // IsUserEnabled && (Flags & ImGuiTableColumnFlags_Disabled) == 0 + bool IsUserEnabled; // Is the column not marked Hidden by the user? (unrelated to being off view, e.g. clipped by scrolling). + bool IsUserEnabledNextFrame; bool IsVisibleX; // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled). bool IsVisibleY; bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not. @@ -2223,12 +2360,13 @@ struct ImGuiTableCellData ImGuiTableColumnIdx Column; // Column number }; -// FIXME-TABLE: transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData +// FIXME-TABLE: more transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData struct ImGuiTable { ImGuiID ID; ImGuiTableFlags Flags; void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[] + ImGuiTableTempData* TempData; // Transient data while table is active. Point within g.CurrentTableStack[] ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. @@ -2280,22 +2418,13 @@ struct ImGuiTable ImRect Bg0ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in BG0/BG1 is cpu-clipped ImRect Bg2ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by widgets relying on regular ClipRect. ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. - ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() - ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() ImRect HostBackupInnerClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() - ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable() - ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable() - ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() - ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() - ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable() - float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable() - int HostBackupItemWidthStackSize;// Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names - ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (FIXME: could be stored outside, worst case we need 1 splitter per recursing table) + ImDrawListSplitter* DrawSplitter; // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly ImGuiTableColumnSortSpecs SortSpecsSingle; - ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would work be good. + ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() ImGuiTableColumnIdx SortSpecsCount; ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) @@ -2309,9 +2438,10 @@ struct ImGuiTable ImGuiTableColumnIdx HeldHeaderColumn; // Index of column header being held. ImGuiTableColumnIdx ReorderColumn; // Index of column being reordered. (not cleared) ImGuiTableColumnIdx ReorderColumnDir; // -1 or +1 + ImGuiTableColumnIdx LeftMostEnabledColumn; // Index of left-most non-hidden column. + ImGuiTableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column. ImGuiTableColumnIdx LeftMostStretchedColumn; // Index of left-most stretched column. ImGuiTableColumnIdx RightMostStretchedColumn; // Index of right-most stretched column. - ImGuiTableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column. ImGuiTableColumnIdx ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot ImGuiTableColumnIdx FreezeRowsRequest; // Requested frozen rows count ImGuiTableColumnIdx FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) @@ -2341,6 +2471,29 @@ struct ImGuiTable IMGUI_API ~ImGuiTable() { IM_FREE(RawData); } }; +// Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table). +// - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. +// - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. +struct ImGuiTableTempData +{ + int TableIndex; // Index in g.Tables.Buf[] pool + float LastTimeActive; // Last timestamp this structure was used + + ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() + ImDrawListSplitter DrawSplitter; + + ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() + ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable() + ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable() + ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable() + float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable() + int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable() + + IMGUI_API ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; } +}; + // sizeof() ~ 12 struct ImGuiTableColumnSettings { @@ -2379,8 +2532,6 @@ struct ImGuiTableSettings ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } }; -#endif // #ifdef IMGUI_HAS_TABLE - //----------------------------------------------------------------------------- // [SECTION] ImGui internal API // No guarantee of forward compatibility here! @@ -2402,7 +2553,6 @@ namespace ImGui IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); IMGUI_API bool IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below); IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); - IMGUI_API ImRect GetWindowAllowedExtentRect(ImGuiWindow* window); IMGUI_API void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond = 0); IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); @@ -2440,6 +2590,8 @@ namespace ImGui IMGUI_API void TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos); IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); IMGUI_API void DestroyPlatformWindow(ImGuiViewportP* viewport); + IMGUI_API void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport); + IMGUI_API const ImGuiPlatformMonitor* GetViewportPlatformMonitor(ImGuiViewport* viewport); // Settings IMGUI_API void MarkIniSettingsDirty(); @@ -2459,11 +2611,11 @@ namespace ImGui IMGUI_API ImVec2 ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_rect); // Basic Accessors - inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemId; } // Get ID of last item (~~ often same ImGui::GetID(label) beforehand) - inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemStatusFlags; } + inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.LastItemData.ID; } // Get ID of last item (~~ often same ImGui::GetID(label) beforehand) + inline ImGuiItemStatusFlags GetItemStatusFlags(){ ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } + inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.InFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } - inline ImGuiItemFlags GetItemsFlags() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.ItemFlags; } IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); IMGUI_API void ClearActiveID(); @@ -2477,21 +2629,33 @@ namespace ImGui // Basic Helpers for widget code IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); IMGUI_API void ItemSize(const ImRect& bb, float text_baseline_y = -1.0f); - IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemAddFlags flags = 0); IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API void ItemFocusable(ImGuiWindow* window, ImGuiID id); IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); - IMGUI_API void SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); - IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id); // Return true if focus is requested - IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); - IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); - IMGUI_API void PopItemFlag(); IMGUI_API bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly) IMGUI_API ImVec2 GetContentRegionMaxAbs(); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); + // Parameter stacks + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + IMGUI_API void PushDisabled(bool disabled = true); + IMGUI_API void PopDisabled(); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): + // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool focused = FocusableItemRegister(...)' + // (New) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' + // Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() + inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Focusable flag to ItemAdd() + inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem +#endif + // Logging/Capture IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer @@ -2506,9 +2670,19 @@ namespace ImGui IMGUI_API bool IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags); IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags); + IMGUI_API ImRect GetPopupAllowedExtentRect(ImGuiWindow* window); IMGUI_API ImGuiWindow* GetTopMostPopupModal(); IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); + IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags); + + // Menus + IMGUI_API bool MenuItemEx(const char* label, const char* icon, const char* shortcut = NULL, bool selected = false, bool enabled = true); + + // Combos + IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); + IMGUI_API bool BeginComboPreview(); + IMGUI_API void EndComboPreview(); // Gamepad/Keyboard Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); @@ -2520,8 +2694,7 @@ namespace ImGui IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. - IMGUI_API void SetNavID(ImGuiID id, int nav_layer, ImGuiID focus_scope_id); - IMGUI_API void SetNavIDWithRectRel(ImGuiID id, int nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); + IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); // Focus Scope (WIP) // This is generally used to identify a selection set (multiple of which may be in the same window), as selection @@ -2534,6 +2707,7 @@ namespace ImGui // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. IMGUI_API void SetItemUsingMouseWheel(); + IMGUI_API void SetActiveIdUsingNavAndKeys(); inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } inline bool IsActiveIdUsingNavInput(ImGuiNavInput input) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavInputMask & (1 << input)) != 0; } inline bool IsActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; IM_ASSERT(key < 64); return (g.ActiveIdUsingKeyInputMask & ((ImU64)1 << key)) != 0; } @@ -2558,9 +2732,10 @@ namespace ImGui IMGUI_API bool DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos); IMGUI_API bool DockNodeBeginAmendTabBar(ImGuiDockNode* node); IMGUI_API void DockNodeEndAmendTabBar(); - inline ImGuiDockNode* DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; } - inline int DockNodeGetDepth(const ImGuiDockNode* node) { int depth = 0; while (node->ParentNode) { node = node->ParentNode; depth++; } return depth; } - inline ImGuiDockNode* GetWindowDockNode() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DockNode; } + inline ImGuiDockNode* DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; } + inline int DockNodeGetDepth(const ImGuiDockNode* node) { int depth = 0; while (node->ParentNode) { node = node->ParentNode; depth++; } return depth; } + inline ImGuiID DockNodeGetWindowMenuButtonId(const ImGuiDockNode* node) { return ImHashStr("#COLLAPSE", 0, node->ID); } + inline ImGuiDockNode* GetWindowDockNode() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DockNode; } IMGUI_API bool GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window); IMGUI_API void BeginDocked(ImGuiWindow* window, bool* p_open); IMGUI_API void BeginDockableDragDropSource(ImGuiWindow* window); @@ -2610,7 +2785,6 @@ namespace ImGui // Tables: Candidates for public API IMGUI_API void TableOpenContextMenu(int column_n = -1); - IMGUI_API void TableSetColumnEnabled(int column_n, bool enabled); IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); IMGUI_API int TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. Return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. @@ -2619,6 +2793,7 @@ namespace ImGui IMGUI_API void TablePopBackgroundChannel(); // Tables: Internals + inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); @@ -2647,6 +2822,7 @@ namespace ImGui IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); IMGUI_API void TableRemove(ImGuiTable* table); IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); IMGUI_API void TableGcCompactSettings(); // Tables: Settings @@ -2665,7 +2841,8 @@ namespace ImGui IMGUI_API void TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); - IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir); + IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset); + IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window); IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button); @@ -2682,7 +2859,7 @@ namespace ImGui IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); - IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. @@ -2709,11 +2886,12 @@ namespace ImGui IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); IMGUI_API void Scrollbar(ImGuiAxis axis); - IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float avail_v, float contents_v, ImDrawCornerFlags rounding_corners); + IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float avail_v, float contents_v, ImDrawFlags flags); IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col); IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); - IMGUI_API ImGuiID GetWindowResizeID(ImGuiWindow* window, int n); // 0..3: corners, 4..7: borders + IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners + IMGUI_API ImGuiID GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); IMGUI_API bool CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value); @@ -2771,13 +2949,15 @@ namespace ImGui // Debug Tools IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); - inline void DebugDrawItemRect(ImU32 col = IM_COL32(255,0,0,255)) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, col); } + inline void DebugDrawItemRect(ImU32 col = IM_COL32(255,0,0,255)) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col); } inline void DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } + IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns); IMGUI_API void DebugNodeDockNode(ImGuiDockNode* node, const char* label); IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); + IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -2817,22 +2997,18 @@ IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table //----------------------------------------------------------------------------- #ifdef IMGUI_ENABLE_TEST_ENGINE -extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ctx, const ImRect& bb, ImGuiID id); -extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags); -extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id); -extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id, const void* data_id_end); -extern void ImGuiTestEngineHook_Log(ImGuiContext* ctx, const char* fmt, ...); +extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ctx, const ImRect& bb, ImGuiID id); +extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags); +extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id); +extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id, const void* data_id_end); +extern void ImGuiTestEngineHook_Log(ImGuiContext* ctx, const char* fmt, ...); #define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemAdd(&g, _BB, _ID) // Register item bounding box #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS) // Register item label and status flags (optional) #define IMGUI_TEST_ENGINE_LOG(_FMT,...) if (g.TestEngineHookItems) ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log -#define IMGUI_TEST_ENGINE_ID_INFO(_ID,_TYPE,_DATA) if (g.TestEngineHookIdInfo == id) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA)); -#define IMGUI_TEST_ENGINE_ID_INFO2(_ID,_TYPE,_DATA,_DATA2) if (g.TestEngineHookIdInfo == id) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA), (const void*)(_DATA2)); +#define IMGUI_TEST_ENGINE_ID_INFO(_ID,_TYPE,_DATA) if (g.TestEngineHookIdInfo == _ID) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA)); +#define IMGUI_TEST_ENGINE_ID_INFO2(_ID,_TYPE,_DATA,_DATA2) if (g.TestEngineHookIdInfo == _ID) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA), (const void*)(_DATA2)); #else -#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) do { } while (0) -#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) do { } while (0) -#define IMGUI_TEST_ENGINE_LOG(_FMT,...) do { } while (0) -#define IMGUI_TEST_ENGINE_ID_INFO(_ID,_TYPE,_DATA) do { } while (0) -#define IMGUI_TEST_ENGINE_ID_INFO2(_ID,_TYPE,_DATA,_DATA2) do { } while (0) +#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) ((void)0) #endif //----------------------------------------------------------------------------- diff --git a/examples/interactive/imgui-1.81/imgui_tables.cpp b/examples/interactive/imgui-1.83/imgui_tables.cpp similarity index 93% rename from examples/interactive/imgui-1.81/imgui_tables.cpp rename to examples/interactive/imgui-1.83/imgui_tables.cpp index 61a84f9f..c7223905 100644 --- a/examples/interactive/imgui-1.81/imgui_tables.cpp +++ b/examples/interactive/imgui-1.83/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (tables and columns code) /* @@ -8,6 +8,7 @@ Index of this file: // [SECTION] Commentary // [SECTION] Header mess // [SECTION] Tables: Main code +// [SECTION] Tables: Simple accessors // [SECTION] Tables: Row changes // [SECTION] Tables: Columns changes // [SECTION] Tables: Columns width management @@ -73,7 +74,7 @@ Index of this file: // (Read carefully because this is subtle but it does make sense!) //----------------------------------------------------------------------------- // About 'outer_size': -// Its meaning needs to differ slightly depending of if we are using ScrollX/ScrollY flags. +// Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags. // Default value is ImVec2(0.0f, 0.0f). // X // - outer_size.x <= 0.0f -> Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge. @@ -90,7 +91,7 @@ Index of this file: // Outer size is also affected by the NoHostExtendX/NoHostExtendY flags. // Important to that note how the two flags have slightly different behaviors! // - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. -// - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible. +// - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible. // In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height. // This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not easily noticeable) //----------------------------------------------------------------------------- @@ -132,7 +133,7 @@ Index of this file: // - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. // - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place! // that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in. -// - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the maximum contents width. +// - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents. // - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weight/widths. //----------------------------------------------------------------------------- // About using column width: @@ -140,9 +141,9 @@ Index of this file: // - you may use GetContentRegionAvail().x to query the width available in a given column. // - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width. // If the column is not resizable and has no width specified with TableSetupColumn(): -// - its width will be automatic and be the set to the max of items submitted. +// - its width will be automatic and be set to the max of items submitted. // - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN). -// - but if the column has one or more item of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN). +// - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN). //----------------------------------------------------------------------------- @@ -161,18 +162,18 @@ Index of this file: // - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing // width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know // it is not going to contribute to row height. -// In many situations, you may skip submitting contents for every columns but one (e.g. the first one). +// In many situations, you may skip submitting contents for every column but one (e.g. the first one). // - Case A: column is not hidden by user, and at least partially in sight (most common case). // - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout output. // - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.). // -// [A] [B] [C] +// [A] [B] [C] // TableNextColumn(): true false false -> [userland] when TableNextColumn() / TableSetColumnIndex() return false, user can skip submitting items but only if the column doesn't contribute to row height. // SkipItems: false false true -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output. // ClipRect: normal zero-width zero-width -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way. // ImDrawList output: normal dummy dummy -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway). // -// - We need distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row. +// - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row. // However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer. //----------------------------------------------------------------------------- // About clipping/culling of whole Tables: @@ -209,6 +210,8 @@ Index of this file: #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif // Clang/GCC warnings with -Weverything @@ -235,6 +238,19 @@ Index of this file: //----------------------------------------------------------------------------- // [SECTION] Tables: Main code //----------------------------------------------------------------------------- +// - TableFixFlags() [Internal] +// - TableFindByID() [Internal] +// - BeginTable() +// - BeginTableEx() [Internal] +// - TableBeginInitMemory() [Internal] +// - TableBeginApplyRequests() [Internal] +// - TableSetupColumnFlags() [Internal] +// - TableUpdateLayout() [Internal] +// - TableUpdateBorders() [Internal] +// - EndTable() +// - TableSetupColumn() +// - TableSetupScrollFreeze() +//----------------------------------------------------------------------------- // Configuration static const int TABLE_DRAW_CHANNEL_BG0 = 0; @@ -272,12 +288,7 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_w flags |= ImGuiTableFlags_NoSavedSettings; // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) -#ifdef IMGUI_HAS_DOCK - ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop; -#else - ImGuiWindow* window_for_settings = outer_window->RootWindow; -#endif - if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings) + if (outer_window->RootWindow->Flags & ImGuiWindowFlags_NoSavedSettings) flags |= ImGuiTableFlags_NoSavedSettings; return flags; @@ -327,6 +338,16 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (instance_no > 0) IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + // Acquire temporary buffers + const int table_idx = g.Tables.GetIndex(table); + g.CurrentTableStackIdx++; + if (g.CurrentTableStackIdx + 1 > g.TablesTempDataStack.Size) + g.TablesTempDataStack.resize(g.CurrentTableStackIdx + 1, ImGuiTableTempData()); + ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempDataStack[g.CurrentTableStackIdx]; + temp_data->TableIndex = table_idx; + table->DrawSplitter = &table->TempData->DrawSplitter; + table->DrawSplitter->Clear(); + // Fix flags table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0; flags = TableFixFlags(flags, outer_window); @@ -340,7 +361,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->ColumnsCount = columns_count; table->IsLayoutLocked = false; table->InnerWidth = inner_width; - table->UserOuterSize = outer_size; + temp_data->UserOuterSize = outer_size; // When not using a child window, WorkRect.Max will grow as we append contents. if (use_child_window) @@ -389,14 +410,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HostIndentX = inner_window->DC.Indent.x; table->HostClipRect = inner_window->ClipRect; table->HostSkipItems = inner_window->SkipItems; - table->HostBackupWorkRect = inner_window->WorkRect; - table->HostBackupParentWorkRect = inner_window->ParentWorkRect; - table->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; - table->HostBackupPrevLineSize = inner_window->DC.PrevLineSize; - table->HostBackupCurrLineSize = inner_window->DC.CurrLineSize; - table->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; - table->HostBackupItemWidth = outer_window->DC.ItemWidth; - table->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; + temp_data->HostBackupWorkRect = inner_window->WorkRect; + temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect; + temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; + temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize; + temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize; + temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; + temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth; + temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); // Padding and Spacing @@ -439,8 +460,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); // Make table current - const int table_idx = g.Tables.GetIndex(table); - g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx)); g.CurrentTable = table; outer_window->DC.CurrentTableIdx = table_idx; if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. @@ -453,13 +472,18 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table_idx >= g.TablesLastTimeActive.Size) g.TablesLastTimeActive.resize(table_idx + 1, -1.0f); g.TablesLastTimeActive[table_idx] = (float)g.Time; + temp_data->LastTimeActive = (float)g.Time; table->MemoryCompacted = false; // Setup memory buffer (clear data if columns count changed) - const int stored_size = table->Columns.size(); - if (stored_size != 0 && stored_size != columns_count) - { - IM_FREE(table->RawData); + ImGuiTableColumn* old_columns_to_preserve = NULL; + void* old_columns_raw_data = NULL; + const int old_columns_count = table->Columns.size(); + if (old_columns_count != 0 && old_columns_count != columns_count) + { + // Attempt to preserve width on column count change (#4046) + old_columns_to_preserve = table->Columns.Data; + old_columns_raw_data = table->RawData; table->RawData = NULL; } if (table->RawData == NULL) @@ -482,14 +506,24 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG for (int n = 0; n < columns_count; n++) { ImGuiTableColumn* column = &table->Columns[n]; - float width_auto = column->WidthAuto; - *column = ImGuiTableColumn(); - column->WidthAuto = width_auto; - column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker + if (old_columns_to_preserve && n < old_columns_count) + { + // FIXME: We don't attempt to preserve column order in this path. + *column = old_columns_to_preserve[n]; + } + else + { + float width_auto = column->WidthAuto; + *column = ImGuiTableColumn(); + column->WidthAuto = width_auto; + column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker + column->IsEnabled = column->IsUserEnabled = column->IsUserEnabledNextFrame = true; + } column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n; - column->IsEnabled = column->IsEnabledNextFrame = true; } } + if (old_columns_raw_data) + IM_FREE(old_columns_raw_data); // Load settings if (table->IsSettingsRequestLoad) @@ -538,9 +572,9 @@ void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) { // Allocate single buffer for our arrays ImSpanAllocator<3> span_allocator; - span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); - span_allocator.ReserveBytes(1, columns_count * sizeof(ImGuiTableColumnIdx)); - span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData)); + span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn)); + span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx)); + span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4); table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes()); span_allocator.SetArenaBasePtr(table->RawData); @@ -635,7 +669,7 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I { IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. } - + // Resize if ((table->Flags & ImGuiTableFlags_Resizable) == 0) flags |= ImGuiTableColumnFlags_NoResize; @@ -687,13 +721,14 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->ColumnsEnabledCount = 0; table->EnabledMaskByIndex = 0x00; table->EnabledMaskByDisplayOrder = 0x00; + table->LeftMostEnabledColumn = -1; table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. // Process columns in their visible orders as we are building the Prev/Next indices. int count_fixed = 0; // Number of columns that have fixed sizing policies int count_stretch = 0; // Number of columns that have stretch sizing policies - int last_visible_column_idx = -1; + int prev_visible_column_idx = -1; bool has_auto_fit_request = false; bool has_resizable = false; float stretch_sum_width_auto = 0.0f; @@ -716,16 +751,18 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->InitStretchWeightOrWidth = -1.0f; } - // Update Enabled state, mark settings/sortspecs dirty + // Update Enabled state, mark settings and sort specs dirty if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) - column->IsEnabledNextFrame = true; - if (column->IsEnabled != column->IsEnabledNextFrame) + column->IsUserEnabledNextFrame = true; + if (column->IsUserEnabled != column->IsUserEnabledNextFrame) { - column->IsEnabled = column->IsEnabledNextFrame; + column->IsUserEnabled = column->IsUserEnabledNextFrame; table->IsSettingsDirty = true; - if (!column->IsEnabled && column->SortOrder != -1) - table->IsSortSpecsDirty = true; } + column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0; + + if (column->SortOrder != -1 && !column->IsEnabled) + table->IsSortSpecsDirty = true; if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) table->IsSortSpecsDirty = true; @@ -741,14 +778,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Mark as enabled and link to previous/next enabled column - column->PrevEnabledColumn = (ImGuiTableColumnIdx)last_visible_column_idx; + column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; column->NextEnabledColumn = -1; - if (last_visible_column_idx != -1) - table->Columns[last_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; + if (prev_visible_column_idx != -1) + table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; + else + table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; table->EnabledMaskByIndex |= (ImU64)1 << column_n; table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; - last_visible_column_idx = column_n; + prev_visible_column_idx = column_n; IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) @@ -778,8 +817,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) table->IsSortSpecsDirty = true; - table->RightMostEnabledColumn = (ImGuiTableColumnIdx)last_visible_column_idx; - IM_ASSERT(table->RightMostEnabledColumn >= 0); + table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; + IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0); // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). @@ -805,7 +844,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Apply same widths policy float width_auto = column->WidthAuto; if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable)) - width_auto = fixed_max_width_auto; + width_auto = fixed_max_width_auto; // Apply automatic width // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) @@ -1084,7 +1123,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Initial state ImGuiWindow* inner_window = table->InnerWindow; if (table->Flags & ImGuiTableFlags_NoClip) - table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); else inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); } @@ -1122,9 +1161,8 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) continue; - if (table->FreezeColumnsCount > 0) - if (column->MaxX < table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsCount - 1]].MaxX) - continue; + if (!column->IsVisibleX && table->LastResizedColumn != column_n) + continue; ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); @@ -1172,6 +1210,7 @@ void ImGui::EndTable() const ImGuiTableFlags flags = table->Flags; ImGuiWindow* inner_window = table->InnerWindow; ImGuiWindow* outer_window = table->OuterWindow; + ImGuiTableTempData* temp_data = table->TempData; IM_ASSERT(inner_window == g.CurrentWindow); IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); @@ -1184,9 +1223,9 @@ void ImGui::EndTable() TableOpenContextMenu((int)table->HoveredColumnBody); // Finalize table height - inner_window->DC.PrevLineSize = table->HostBackupPrevLineSize; - inner_window->DC.CurrLineSize = table->HostBackupCurrLineSize; - inner_window->DC.CursorMaxPos = table->HostBackupCursorMaxPos; + inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; + inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; + inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; const float inner_content_max_y = table->RowPosY2; IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); if (inner_window != outer_window) @@ -1233,10 +1272,11 @@ void ImGui::EndTable() #endif // Flatten channels and merge draw calls - table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); + ImDrawListSplitter* splitter = table->DrawSplitter; + splitter->SetCurrentChannel(inner_window->DrawList, 0); if ((table->Flags & ImGuiTableFlags_NoClip) == 0) TableMergeDrawChannels(table); - table->DrawSplitter.Merge(inner_window->DrawList); + splitter->Merge(inner_window->DrawList); // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable() const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); @@ -1278,18 +1318,18 @@ void ImGui::EndTable() // Pop from id stack IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); - IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= table->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); + IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); PopID(); // Restore window data that we modified const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos; - inner_window->WorkRect = table->HostBackupWorkRect; - inner_window->ParentWorkRect = table->HostBackupParentWorkRect; + inner_window->WorkRect = temp_data->HostBackupWorkRect; + inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; - outer_window->DC.ItemWidth = table->HostBackupItemWidth; - outer_window->DC.ItemWidthStack.Size = table->HostBackupItemWidthStackSize; - outer_window->DC.ColumnsOffset = table->HostBackupColumnsOffset; + outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth; + outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize; + outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset; // Layout in outer window // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding @@ -1312,20 +1352,20 @@ void ImGui::EndTable() IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0); outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth); } - else if (table->UserOuterSize.x <= 0.0f) + else if (temp_data->UserOuterSize.x <= 0.0f) { const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f; - outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - table->UserOuterSize.x); + outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x); outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth)); } else { outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x); } - if (table->UserOuterSize.y <= 0.0f) + if (temp_data->UserOuterSize.y <= 0.0f) { const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f; - outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - table->UserOuterSize.y); + outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y)); } else @@ -1341,8 +1381,15 @@ void ImGui::EndTable() // Clear or restore current table, if any IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); - g.CurrentTableStack.pop_back(); - g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; + IM_ASSERT(g.CurrentTableStackIdx >= 0); + g.CurrentTableStackIdx--; + temp_data = g.CurrentTableStackIdx >= 0 ? &g.TablesTempDataStack[g.CurrentTableStackIdx] : NULL; + g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; + if (g.CurrentTable) + { + g.CurrentTable->TempData = temp_data; + g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter; + } outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; } @@ -1398,7 +1445,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo // Init default visibility/sort state if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) - column->IsEnabled = column->IsEnabledNextFrame = false; + column->IsUserEnabled = column->IsUserEnabledNextFrame = false; if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) { column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. @@ -1425,13 +1472,38 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit - table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)columns : 0; + table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)ImMin(columns, table->ColumnsCount) : 0; table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0; table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b + + // Ensure frozen columns are ordered in their section. We still allow multiple frozen columns to be reordered. + for (int column_n = 0; column_n < table->FreezeColumnsRequest; column_n++) + { + int order_n = table->DisplayOrderToIndex[column_n]; + if (order_n != column_n && order_n >= table->FreezeColumnsRequest) + { + ImSwap(table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder, table->Columns[table->DisplayOrderToIndex[column_n]].DisplayOrder); + ImSwap(table->DisplayOrderToIndex[order_n], table->DisplayOrderToIndex[column_n]); + } + } } +//----------------------------------------------------------------------------- +// [SECTION] Tables: Simple accessors +//----------------------------------------------------------------------------- +// - TableGetColumnCount() +// - TableGetColumnName() +// - TableGetColumnName() [Internal] +// - TableSetColumnEnabled() +// - TableGetColumnFlags() +// - TableGetCellBgRect() [Internal] +// - TableGetColumnResizeID() [Internal] +// - TableGetHoveredColumn() [Internal] +// - TableSetBgColor() +//----------------------------------------------------------------------------- + int ImGui::TableGetColumnCount() { ImGuiContext& g = *GImGui; @@ -1460,7 +1532,12 @@ const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) return &table->ColumnsNames.Buf[column->NameOffset]; } -// For the getter you can use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) +// Change user accessible enabled/disabled state of a column (often perceived as "showing/hiding" from users point of view) +// Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) +// - Require table to have the ImGuiTableFlags_Hideable flag because we are manipulating user accessible state. +// - Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable(). +// - For the getter you can test (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) != 0. +// - Alternative: the ImGuiTableColumnFlags_Disabled is an overriding/master disable flag which will also hide the column from context menu. void ImGui::TableSetColumnEnabled(int column_n, bool enabled) { ImGuiContext& g = *GImGui; @@ -1468,11 +1545,12 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) IM_ASSERT(table != NULL); if (!table) return; + IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above if (column_n < 0) column_n = table->CurrentColumn; IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); ImGuiTableColumn* column = &table->Columns[column_n]; - column->IsEnabledNextFrame = enabled; + column->IsUserEnabledNextFrame = enabled; } // We allow querying for an extra column in order to poll the IsHovered state of the right-most section @@ -1698,7 +1776,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. if ((table->Flags & ImGuiTableFlags_NoClip) == 0) window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4(); - table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); + table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); } // Draw row background @@ -1770,7 +1848,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); - table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); } if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) @@ -1878,21 +1956,22 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) window->SkipItems = column->IsSkipItems; if (column->IsSkipItems) { - window->DC.LastItemId = 0; - window->DC.LastItemStatusFlags = 0; + ImGuiContext& g = *GImGui; + g.LastItemData.ID = 0; + g.LastItemData.StatusFlags = 0; } if (table->Flags & ImGuiTableFlags_NoClip) { // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. - table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP); } else { // FIXME-TABLE: Could avoid this if draw channel is dummy channel? SetWindowClipRectBeforeSetChannel(window, column->ClipRect); - table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } // Logging @@ -1945,6 +2024,7 @@ float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n) if (table->Flags & ImGuiTableFlags_ScrollX) { // Frozen columns can't reach beyond visible width else scrolling will naturally break. + // (we use DisplayOrder as within a set of multiple frozen column reordering is possible) if (column->DisplayOrder < table->FreezeColumnsRequest) { max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX; @@ -2140,7 +2220,7 @@ void ImGui::TablePushBackgroundChannel() // Optimization: avoid SetCurrentChannel() + PushClipRect() table->HostBackupInnerClipRect = window->ClipRect; SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd); - table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent); } void ImGui::TablePopBackgroundChannel() @@ -2152,7 +2232,7 @@ void ImGui::TablePopBackgroundChannel() // Optimization: avoid PopClipRect() + SetCurrentChannel() SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); - table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } // Allocate draw channels. Called by TableUpdateLayout() @@ -2178,7 +2258,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) const int channels_for_bg = 1 + 1 * freeze_row_multiplier; const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0; const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; - table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); + table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total); table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1); table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN; table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN); @@ -2242,7 +2322,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) void ImGui::TableMergeDrawChannels(ImGuiTable* table) { ImGuiContext& g = *GImGui; - ImDrawListSplitter* splitter = &table->DrawSplitter; + ImDrawListSplitter* splitter = table->DrawSplitter; const bool has_freeze_v = (table->FreezeRowsCount > 0); const bool has_freeze_h = (table->FreezeColumnsCount > 0); IM_ASSERT(splitter->_Current == 0); @@ -2253,10 +2333,11 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) ImRect ClipRect; int ChannelsCount; ImBitArray ChannelsMask; + + MergeGroup() { ChannelsCount = 0; } }; int merge_group_mask = 0x00; MergeGroup merge_groups[4]; - memset(merge_groups, 0, sizeof(merge_groups)); // 1. Scan channels and take note of those which can be merged for (int column_n = 0; column_n < table->ColumnsCount; column_n++) @@ -2334,7 +2415,6 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; ImBitArray remaining_mask; // We need 132-bit of storage - remaining_mask.ClearAllBits(); remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen); IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); @@ -2364,7 +2444,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); #if 0 - GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 1.0f); + GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); #endif @@ -2413,7 +2493,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) return; ImDrawList* inner_drawlist = inner_window->DrawList; - table->DrawSplitter.SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); + table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false); // Draw inner border and resizing feedback @@ -2433,7 +2513,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const bool is_hovered = (table->HoveredColumnBorder == column_n); const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; - const bool is_frozen_separator = (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1); + const bool is_frozen_separator = (table->FreezeColumnsCount == order_n + 1); if (column->MaxX > table->InnerClipRect.Max.x && !is_resized) continue; @@ -2477,7 +2557,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { - inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size); + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { @@ -2529,8 +2609,7 @@ ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() if (!table->IsLayoutLocked) TableUpdateLayout(table); - if (table->IsSortSpecsDirty) - TableSortSpecsBuild(table); + TableSortSpecsBuild(table); return &table->SortSpecs; } @@ -2669,28 +2748,33 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) void ImGui::TableSortSpecsBuild(ImGuiTable* table) { - IM_ASSERT(table->IsSortSpecsDirty); - TableSortSpecsSanitize(table); + bool dirty = table->IsSortSpecsDirty; + if (dirty) + { + TableSortSpecsSanitize(table); + table->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount); + table->SortSpecs.SpecsDirty = true; // Mark as dirty for user + table->IsSortSpecsDirty = false; // Mark as not dirty for us + } // Write output - table->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount); ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &table->SortSpecsSingle : table->SortSpecsMulti.Data; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->SortOrder == -1) - continue; - IM_ASSERT(column->SortOrder < table->SortSpecsCount); - ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder]; - sort_spec->ColumnUserID = column->UserID; - sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; - sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; - sort_spec->SortDirection = column->SortDirection; - } + if (dirty && sort_specs != NULL) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + IM_ASSERT(column->SortOrder < table->SortSpecsCount); + ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; + sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; + sort_spec->SortDirection = column->SortDirection; + } + table->SortSpecs.Specs = sort_specs; table->SortSpecs.SpecsCount = table->SortSpecsCount; - table->SortSpecs.SpecsDirty = true; // Mark as dirty for user - table->IsSortSpecsDirty = false; // Mark as not dirty for us } //------------------------------------------------------------------------- @@ -2710,8 +2794,11 @@ float ImGui::TableGetHeaderRowHeight() float row_height = GetTextLineHeight(); int columns_count = TableGetColumnCount(); for (int column_n = 0; column_n < columns_count; column_n++) - if (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_IsEnabled) + { + ImGuiTableColumnFlags flags = TableGetColumnFlags(column_n); + if ((flags & ImGuiTableColumnFlags_IsEnabled) && !(flags & ImGuiTableColumnFlags_NoHeaderLabel)) row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + } row_height += GetStyle().CellPadding.y * 2.0f; return row_height; } @@ -2748,7 +2835,7 @@ void ImGui::TableHeadersRow() // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. - const char* name = TableGetColumnName(column_n); + const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? "" : TableGetColumnName(column_n); PushID(table->InstanceCurrent * table->ColumnsCount + column_n); TableHeader(name); PopID(); @@ -2830,7 +2917,6 @@ void ImGui::TableHeader(const char* label) const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); //RenderFrame(bb.Min, bb.Max, col, false, 0.0f); TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); } else { @@ -2838,6 +2924,7 @@ void ImGui::TableHeader(const char* label) if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); } + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; @@ -3003,16 +3090,19 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column->Flags & ImGuiTableColumnFlags_Disabled) + continue; + const char* name = TableGetColumnName(table, other_column_n); if (name == NULL || name[0] == 0) name = ""; // Make sure we can't hide the last active column bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; - if (other_column->IsEnabled && table->ColumnsEnabledCount <= 1) + if (other_column->IsUserEnabled && table->ColumnsEnabledCount <= 1) menu_item_active = false; - if (MenuItem(name, NULL, other_column->IsEnabled, menu_item_active)) - other_column->IsEnabledNextFrame = !other_column->IsEnabled; + if (MenuItem(name, NULL, other_column->IsUserEnabled, menu_item_active)) + other_column->IsUserEnabledNextFrame = !other_column->IsUserEnabled; } PopItemFlag(); } @@ -3137,7 +3227,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; column_settings->SortDirection = column->SortDirection; - column_settings->IsEnabled = column->IsEnabled; + column_settings->IsEnabled = column->IsUserEnabled; column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) save_ref_scale = true; @@ -3151,7 +3241,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column->SortOrder != -1) settings->SaveFlags |= ImGuiTableFlags_Sortable; - if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; } settings->SaveFlags &= table->Flags; @@ -3209,7 +3299,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) else column->DisplayOrder = (ImGuiTableColumnIdx)column_n; display_order_mask |= (ImU64)1 << column->DisplayOrder; - column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled; + column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled; column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } @@ -3228,8 +3318,9 @@ void ImGui::TableLoadSettings(ImGuiTable* table) static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; - for (int i = 0; i != g.Tables.GetSize(); i++) - g.Tables.GetByIndex(i)->SettingsOffset = -1; + for (int i = 0; i != g.Tables.GetMapSize(); i++) + if (ImGuiTable* table = g.Tables.TryGetMapData(i)) + table->SettingsOffset = -1; g.SettingsTables.clear(); } @@ -3237,12 +3328,12 @@ static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandle static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; - for (int i = 0; i != g.Tables.GetSize(); i++) - { - ImGuiTable* table = g.Tables.GetByIndex(i); - table->IsSettingsRequestLoad = true; - table->SettingsOffset = -1; - } + for (int i = 0; i != g.Tables.GetMapSize(); i++) + if (ImGuiTable* table = g.Tables.TryGetMapData(i)) + { + table->IsSettingsRequestLoad = true; + table->SettingsOffset = -1; + } } static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) @@ -3315,6 +3406,9 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); + if (!save_column) + continue; buf->appendf("Column %-2d", column_n); if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); if (save_size && column->IsStretch) buf->appendf(" Weight=%.4f", column->WidthOrWeight); @@ -3368,10 +3462,9 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID); ImGuiContext& g = *GImGui; IM_ASSERT(table->MemoryCompacted == false); - table->DrawSplitter.ClearFreeMemory(); - table->SortSpecsMulti.clear(); table->SortSpecs.Specs = NULL; - table->IsSortSpecsDirty = true; + table->SortSpecsMulti.clear(); + table->IsSortSpecsDirty = true; // FIXME: shouldn't have to leak into user performing a sort table->ColumnsNames.clear(); table->MemoryCompacted = true; for (int n = 0; n < table->ColumnsCount; n++) @@ -3379,6 +3472,12 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f; } +void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) +{ + temp_data->DrawSplitter.ClearFreeMemory(); + temp_data->LastTimeActive = -1.0f; +} + // Compact and remove unused settings data (currently only used by TestEngine) void ImGui::TableGcCompactSettings() { diff --git a/examples/interactive/imgui-1.81/imgui_widgets.cpp b/examples/interactive/imgui-1.83/imgui_widgets.cpp similarity index 90% rename from examples/interactive/imgui-1.81/imgui_widgets.cpp rename to examples/interactive/imgui-1.83/imgui_widgets.cpp index b6175368..c5d7b24d 100644 --- a/examples/interactive/imgui-1.81/imgui_widgets.cpp +++ b/examples/interactive/imgui-1.83/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.81 +// dear imgui, v1.84 WIP // (widgets code) /* @@ -59,6 +59,8 @@ Index of this file: #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif // Clang/GCC warnings with -Weverything @@ -122,7 +124,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); //------------------------------------------------------------------------- // For InputTextEx() -static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); @@ -350,17 +352,20 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const ImGuiStyle& style = g.Style; const float w = CalcItemWidth(); + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2)); - const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y * 2) + label_size); + + const ImVec2 pos = window->DC.CursorPos; + const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); + const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, 0)) return; // Render - const char* value_text_begin = &g.TempBuffer[0]; - const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f, 0.5f)); + RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); } @@ -483,14 +488,6 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - if (flags & ImGuiButtonFlags_Disabled) - { - if (out_hovered) *out_hovered = false; - if (out_held) *out_held = false; - if (g.ActiveId == id) ClearActiveID(); - return false; - } - // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonDefault_; @@ -500,12 +497,12 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool flags |= ImGuiButtonFlags_PressedOnDefault_; ImGuiWindow* backup_hovered_window = g.HoveredWindow; - const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window->RootWindow; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree; if (flatten_hovered_children) g.HoveredWindow = window; #ifdef IMGUI_ENABLE_TEST_ENGINE - if (id != 0 && window->DC.LastItemId != id) + if (id != 0 && g.LastItemData.ID != id) IMGUI_TEST_ENGINE_ITEM_ADD(bb, id); #endif @@ -522,7 +519,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool { hovered = true; SetHoveredID(id); - if (CalcTypematicRepeatAmount(g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, g.HoveredIdTimer + 0.0001f, DRAGDROP_HOLD_TO_OPEN_TIMER, 0.00f)) + if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER) { pressed = true; g.DragDropHoldJustPressedId = id; @@ -683,8 +680,9 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags if (!ItemAdd(bb, id)) return false; - if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); @@ -701,7 +699,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) // CloseCurrentPopup(); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } @@ -759,7 +757,7 @@ bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiBu if (!ItemAdd(bb, id)) return false; - if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; bool hovered, held; @@ -782,22 +780,30 @@ bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) } // Button to close a window -bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)//, float size) +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. - // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) + // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); - bool is_clipped = !ItemAdd(bb, id); + ImRect bb_interact = bb; + const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); + if (area_to_visible_ratio < 1.5f) + bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f)); + + // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + bool is_clipped = !ItemAdd(bb_interact, id); bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); + bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held); if (is_clipped) return pressed; // Render + // FIXME: Clarify this mess ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); ImVec2 center = bb.GetCenter(); if (hovered) @@ -825,12 +831,11 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no // Render //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); - ImVec2 off = dock_node ? ImVec2(IM_FLOOR(-g.Style.ItemInnerSpacing.x * 0.5f) + 0.5f, 0.0f) : ImVec2(0.0f, 0.0f); ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); ImVec2 center = bb.GetCenter(); if (hovered || held) - window->DrawList->AddCircleFilled(center + off + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, bg_col, 12); + window->DrawList->AddCircleFilled(center + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, bg_col, 12); if (dock_node) RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, text_col); @@ -873,19 +878,19 @@ void ImGui::Scrollbar(ImGuiAxis axis) // Calculate scrollbar bounding box ImRect bb = GetWindowScrollbarRect(window, axis); - ImDrawCornerFlags rounding_corners = 0; + ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; if (axis == ImGuiAxis_X) { - rounding_corners |= ImDrawCornerFlags_BotLeft; + rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; if (!window->ScrollbarY) - rounding_corners |= ImDrawCornerFlags_BotRight; + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; } else { if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) - rounding_corners |= ImDrawCornerFlags_TopRight; + rounding_corners |= ImDrawFlags_RoundCornersTopRight; if (!window->ScrollbarX) - rounding_corners |= ImDrawCornerFlags_BotRight; + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; } float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; @@ -898,7 +903,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. // Still, the code should probably be made simpler.. -bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawCornerFlags rounding_corners) +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -978,7 +983,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, floa // Render const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); - window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, rounding_corners); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); ImRect grab_rect; if (axis == ImGuiAxis_X) grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); @@ -1077,7 +1082,7 @@ bool ImGui::Checkbox(const char* label, bool* v) ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id)) { - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return false; } @@ -1093,7 +1098,7 @@ bool ImGui::Checkbox(const char* label, bool* v) RenderNavHighlight(total_bb, id); RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); - bool mixed_value = (window->DC.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; if (mixed_value) { // Undocumented tristate/mixed/indeterminate checkbox (#2644) @@ -1113,7 +1118,7 @@ bool ImGui::Checkbox(const char* label, bool* v) if (label_size.x > 0.0f) RenderText(label_pos, label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; } @@ -1125,11 +1130,11 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) bool pressed; if (!all_on && any_on) { - ImGuiWindow* window = GetCurrentWindow(); - ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_MixedValue; + ImGuiContext& g = *GImGui; + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_MixedValue; pressed = Checkbox(label, &all_on); - window->DC.ItemFlags = backup_item_flags; + g.CurrentItemFlags = backup_item_flags; } else { @@ -1215,7 +1220,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (label_size.x > 0.0f) RenderText(label_pos, label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } @@ -1427,10 +1432,10 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; bool item_add = ItemAdd(bb, id); - window->DC.ItemFlags = item_flags_backup; + g.CurrentItemFlags = item_flags_backup; if (!item_add) return false; @@ -1438,10 +1443,12 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float ImRect bb_interact = bb; bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (hovered) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb if (g.ActiveId != id) SetItemAllowOverlap(); - if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) + if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); ImRect bb_render = bb; @@ -1531,8 +1538,12 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc //------------------------------------------------------------------------- // [SECTION] Widgets: ComboBox //------------------------------------------------------------------------- +// - CalcMaxPopupHeightFromItemCount() [Internal] // - BeginCombo() +// - BeginComboPopup() [Internal] // - EndCombo() +// - BeginComboPreview() [Internal] +// - EndComboPreview() [Internal] // - Combo() //------------------------------------------------------------------------- @@ -1546,79 +1557,99 @@ static float CalcMaxPopupHeightFromItemCount(int items_count) bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) { - // Always consume the SetNextWindowSizeConstraint() call in our early return paths ImGuiContext& g = *GImGui; - bool has_window_size_constraint = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) != 0; - g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; - ImGuiWindow* window = GetCurrentWindow(); + + ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags; + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values if (window->SkipItems) return false; - IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together - const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float expected_w = CalcItemWidth(); - const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w; - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth(); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) + if (!ItemAdd(total_bb, id, &bb)) return false; + // Open on click bool hovered, held; - bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); - bool popup_open = IsPopupOpen(id, ImGuiPopupFlags_None); + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); + bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); + if ((pressed || g.NavActivateId == id) && !popup_open) + { + OpenPopupEx(popup_id, ImGuiPopupFlags_None); + popup_open = true; + } + // Render shape const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size); - RenderNavHighlight(frame_bb, id); + const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); + RenderNavHighlight(bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) - window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Left); + window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); if (!(flags & ImGuiComboFlags_NoArrowButton)) { ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); - window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right); - if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x) - RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); + window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) + RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); + } + RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); + + // Custom preview + if (flags & ImGuiComboFlags_CustomPreview) + { + g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); + IM_ASSERT(preview_value == NULL || preview_value[0] == 0); + preview_value = NULL; } - RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); + + // Render preview and label if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) { - ImVec2 preview_pos = frame_bb.Min + style.FramePadding; if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); - RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f)); + RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); } if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - if ((pressed || g.NavActivateId == id) && !popup_open) - { - if (window->DC.NavLayerCurrent == 0) - window->NavLastIds[0] = id; - OpenPopupEx(id, ImGuiPopupFlags_None); - popup_open = true; - } + RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); if (!popup_open) return false; - if (has_window_size_constraint) + g.NextWindowData.Flags = backup_next_window_data_flags; + return BeginComboPopup(popup_id, bb, flags); +} + +bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None)) + { + g.NextWindowData.ClearFlags(); + return false; + } + + // Set popup size + float w = bb.GetWidth(); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) { - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); } else { if ((flags & ImGuiComboFlags_HeightMask_) == 0) flags |= ImGuiComboFlags_HeightRegular; - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one int popup_max_height_in_items = -1; if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; @@ -1626,30 +1657,27 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); } + // This is essentially a specialized version of BeginPopupEx() char name[16]; ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth - // Position the window given a custom constraint (peak into expected window size so we can position it) - // This might be easier to express with an hypothetical SetNextWindowPosConstraints() function. + // Set position given a custom constraint (peak into expected window size so we can position it) + // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? + // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()? if (ImGuiWindow* popup_window = FindWindowByName(name)) if (popup_window->WasActive) { // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); - if (flags & ImGuiComboFlags_PopupAlignLeft) - popup_window->AutoPosLastDirection = ImGuiDir_Left; // "Below, Toward Left" - else - popup_window->AutoPosLastDirection = ImGuiDir_Down; // "Below, Toward Right (default)" - ImRect r_outer = GetWindowAllowedExtentRect(popup_window); - ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)" + ImRect r_outer = GetPopupAllowedExtentRect(popup_window); + ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox); SetNextWindowPos(pos); } // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; - - // Horizontally align ourselves with the framed text - PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text bool ret = Begin(name, NULL, window_flags); PopStyleVar(); if (!ret) @@ -1666,6 +1694,57 @@ void ImGui::EndCombo() EndPopup(); } +// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements +// (Experimental, see GitHub issues: #1658, #4168) +bool ImGui::BeginComboPreview() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + + if (window->SkipItems || !window->ClipRect.Overlaps(g.LastItemData.Rect)) // FIXME: Because we don't have a ImGuiItemStatusFlags_Visible flag to test last ItemAdd() result + return false; + IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? + if (!window->ClipRect.Contains(preview_data->PreviewRect)) // Narrower test (optional) + return false; + + // FIXME: This could be contained in a PushWorkRect() api + preview_data->BackupCursorPos = window->DC.CursorPos; + preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos; + preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; + preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; + preview_data->BackupLayout = window->DC.LayoutType; + window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true); + + return true; +} + +void ImGui::EndComboPreview() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + + // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future + ImDrawList* draw_list = window->DrawList; + if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y) + if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command + { + draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect; + draw_list->_TryMergeDrawCmds(); + } + PopClipRect(); + window->DC.CursorPos = preview_data->BackupCursorPos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; + window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->DC.LayoutType = preview_data->BackupLayout; + preview_data->PreviewRect = ImRect(); +} + // Getter for the old Combo() API: const char*[] static bool Items_ArrayGetter(void* data, int idx, const char** out_text) { @@ -1734,8 +1813,9 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi } EndCombo(); + if (value_changed) - MarkItemEdited(g.CurrentWindow->DC.LastItemId); + MarkItemEdited(g.LastItemData.ID); return value_changed; } @@ -1789,7 +1869,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64 { sizeof(ImU64), "U64", "%llu", "%llu" }, #endif - { sizeof(float), "float", "%f", "%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) + { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); @@ -1976,13 +2056,15 @@ bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_b { // All other types assign constant // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future. - sscanf(buf, format, p_data); + if (sscanf(buf, format, p_data) < 1) + return false; } else { // Small types need a 32-bit buffer to receive the result from scanf() int v32; - sscanf(buf, format, &v32); + if (sscanf(buf, format, &v32) < 1) + return false; if (data_type == ImGuiDataType_S8) *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); else if (data_type == ImGuiDataType_U8) @@ -2076,12 +2158,36 @@ static const char* ImAtoi(const char* src, TYPE* output) return src; } +// Sanitize format +// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi +// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi. +static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size) +{ + IM_UNUSED(fmt_out_size); + const char* fmt_end = ImParseFormatFindEnd(fmt); + IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + while (fmt < fmt_end) + { + char c = *(fmt++); + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *fmt_out = 0; // Zero-terminate +} + template TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) { const char* fmt_start = ImParseFormatFindStart(format); if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string return v; + + // Sanitize format + char fmt_sanitized[32]; + SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); + fmt_start = fmt_sanitized; + + // Format value with our rounding, and read back char v_str[64]; ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); const char* p = v_str; @@ -2119,9 +2225,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const { ImGuiContext& g = *GImGui; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; - const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); const bool is_clamped = (v_min < v_max); - const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) && is_decimal; + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); // Default tweak speed if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) @@ -2139,7 +2245,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { - int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis]; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -2179,7 +2285,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (is_logarithmic) { // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 1; + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); // Convert to parametric space, apply delta, convert back @@ -2217,9 +2323,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const // Clamp values (+ handle overflow/wrap-around for integer types) if (*v != v_cur && is_clamped) { - if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal)) + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) v_cur = v_min; - if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal)) + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) v_cur = v_max; } @@ -2245,7 +2351,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v } if (g.ActiveId != id) return false; - if ((g.CurrentWindow->DC.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) @@ -2278,12 +2384,14 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) return false; // Default format string when passing NULL @@ -2294,11 +2402,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Tabbing or CTRL-clicking on Drag turns it into an InputText const bool hovered = ItemHoverable(frame_bb, id); - const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { - const bool focus_requested = temp_input_allowed && FocusableItemRegister(window, id); + const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0; const bool clicked = (hovered && g.IO.MouseClicked[0]); const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]); if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id) @@ -2308,10 +2415,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, FocusWindow(window); g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id)) - { temp_input_is_active = true; - FocusableItemUnregister(window); - } } // Experimental: simple click (without moving) turns Drag into an InputText // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings. @@ -2320,7 +2424,6 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, { g.NavInputId = id; temp_input_is_active = true; - FocusableItemUnregister(window); } } @@ -2332,7 +2435,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, } // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); @@ -2351,7 +2454,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } @@ -2439,6 +2542,7 @@ bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_cu TextEx(label, FindRenderedTextEnd(label)); EndGroup(); PopID(); + return value_changed; } @@ -2610,7 +2714,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T { if (v_min == v_max) return v_min; - const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); TYPE result; if (is_logarithmic) @@ -2658,7 +2762,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T else { // Linear slider - if (is_decimal) + if (is_floating_point) { result = ImLerp(v_min, v_max, t); } @@ -2691,14 +2795,14 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ const ImGuiStyle& style = g.Style; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; - const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); - const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) && is_decimal; + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); const float grab_padding = 2.0f; const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; float grab_sz = style.GrabMinSize; SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); - if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows + if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit grab_sz = ImMin(grab_sz, slider_sz); const float slider_usable_sz = slider_sz - grab_sz; @@ -2710,7 +2814,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ if (is_logarithmic) { // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 1; + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f); } @@ -2748,7 +2852,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y; if (input_delta != 0.0f) { - const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; if (decimal_precision > 0) { input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds @@ -2851,7 +2955,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); ImGuiContext& g = *GImGui; - if ((g.CurrentWindow->DC.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) @@ -2901,8 +3005,9 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) return false; // Default format string when passing NULL @@ -2913,11 +3018,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat // Tabbing or CTRL-clicking on Slider turns it into an input box const bool hovered = ItemHoverable(frame_bb, id); - const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { - const bool focus_requested = temp_input_allowed && FocusableItemRegister(window, id); + const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0; const bool clicked = (hovered && g.IO.MouseClicked[0]); if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id) { @@ -2926,10 +3030,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)) - { temp_input_is_active = true; - FocusableItemUnregister(window); - } } } @@ -2941,7 +3042,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat } // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); @@ -2965,7 +3066,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } @@ -3089,7 +3190,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d } // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); @@ -3256,7 +3357,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* ClearActiveID(); g.CurrentWindow->DC.CursorPos = bb.Min; - bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags); + bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); if (init) { // First frame we started displaying the InputText widget, we expect it to take the active id. @@ -3293,7 +3394,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL); if (p_clamp_min || p_clamp_max) { - if (DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) + if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) ImSwap(p_clamp_min, p_clamp_max); DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); } @@ -3344,7 +3445,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data style.FramePadding.x = style.FramePadding.y; ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) - button_flags |= ImGuiButtonFlags_Disabled; + PushDisabled(true); SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) { @@ -3357,6 +3458,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } + if (flags & ImGuiInputTextFlags_ReadOnly) + PopDisabled(); const char* label_end = FindRenderedTextEnd(label); if (label != label_end) @@ -3375,7 +3478,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format); } if (value_changed) - MarkItemEdited(window->DC.LastItemId); + MarkItemEdited(g.LastItemData.ID); return value_changed; } @@ -3553,12 +3656,12 @@ static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* t namespace ImStb { -static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } -static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { return obj->TextW[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; -static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { const ImWchar* text = obj->TextW.Data; const ImWchar* text_remaining = NULL; @@ -3571,19 +3674,20 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* ob r->num_chars = (int)(text_remaining - (text + line_start_idx)); } +// When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } -static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; } -static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } #ifdef __APPLE__ // FIXME: Move setting to IO structure -static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } #else -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } #endif #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL -static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { ImWchar* dst = obj->TextW.Data + pos; @@ -3599,9 +3703,9 @@ static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) *dst = '\0'; } -static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len) { - const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0; + const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int text_len = obj->CurLenW; IM_ASSERT(pos <= text_len); @@ -3655,7 +3759,7 @@ static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const Im // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) -static void stb_textedit_replace(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len) +static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len) { stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); @@ -3738,8 +3842,9 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons } // Return false to discard a character. -static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source) { + IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); unsigned int c = *p_char; // Filter non-printable (NB: isprint is unreliable! see #2467) @@ -3752,15 +3857,18 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f return false; } - // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) - if (c == 127) - return false; + if (input_source != ImGuiInputSource_Clipboard) + { + // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) + if (c == 127) + return false; - // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) - if (c >= 0xE000 && c <= 0xF8FF) - return false; + // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) + if (c >= 0xE000 && c <= 0xF8FF) + return false; + } - // Filter Unicode ranges we are not handling in this build. + // Filter Unicode ranges we are not handling in this build if (c > IM_UNICODE_CODEPOINT_MAX) return false; @@ -3864,7 +3972,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 inner_size = frame_size; if (is_multiline) { - if (!ItemAdd(total_bb, id, &frame_bb)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) { ItemSize(total_bb, style.FramePadding.y); EndGroup(); @@ -3885,15 +3993,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ return false; } draw_window = g.CurrentWindow; // Child window - draw_window->DC.NavLayerActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. + draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. draw_window->DC.CursorPos += style.FramePadding; inner_size.x -= draw_window->ScrollbarSizes.x; } else { + // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) - return false; + if (!(flags & ImGuiInputTextFlags_MergedItem)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) + return false; } const bool hovered = ItemHoverable(frame_bb, id); if (hovered) @@ -3902,12 +4012,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We are only allowed to access the state if we are already the active widget. ImGuiInputTextState* state = GetInputTextState(id); - const bool focus_requested = FocusableItemRegister(window, id); - const bool focus_requested_by_code = focus_requested && (g.FocusRequestCurrWindow == window && g.FocusRequestCurrCounterRegular == window->DC.FocusCounterRegular); - const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + const bool focus_requested_by_code = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByCode) != 0; + const bool focus_requested_by_tabbing = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); + const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard)); const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); @@ -3917,7 +4026,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); - const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start); + const bool init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing); const bool init_state = (init_make_active || user_scroll_active); if ((init_state && g.ActiveId != id) || init_changed_specs) { @@ -3956,9 +4065,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_multiline && focus_requested_by_code) select_all = true; } - if (flags & ImGuiInputTextFlags_AlwaysInsertMode) - state->Stb.insert_mode = 1; - if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + if (flags & ImGuiInputTextFlags_AlwaysOverwrite) + state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) + if (!is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl))) select_all = true; } @@ -4036,7 +4145,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ backup_current_text_length = state->CurLenA; state->Edited = false; state->BufCapacityA = buf_size; - state->UserFlags = flags; + state->Flags = flags; state->UserCallback = callback; state->UserCallbackData = callback_user_data; @@ -4085,7 +4194,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!io.InputQueueCharacters.contains('\t')) { unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) state->OnKeyPressed((int)c); } @@ -4100,7 +4209,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (c == '\t' && io.KeyShift) continue; - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) state->OnKeyPressed((int)c); } @@ -4164,7 +4273,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (!is_readonly) { unsigned int c = '\n'; // Insert new line - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) state->OnKeyPressed((int)c); } } @@ -4217,7 +4326,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ s += ImTextCharFromUtf8(&c, s, NULL); if (c == 0) break; - if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; } @@ -4389,7 +4498,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Clear temporary user storage - state->UserFlags = 0; + state->Flags = ImGuiInputTextFlags_None; state->UserCallback = NULL; state->UserCallbackData = NULL; } @@ -4573,7 +4682,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; - ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll; + ImVec2 cursor_screen_pos = ImFloor(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); @@ -4626,7 +4735,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) MarkItemEdited(id); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) return enter_pressed; else @@ -4677,24 +4786,24 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // If we're not showing any slider there's no point in doing any HSV conversions const ImGuiColorEditFlags flags_untouched = flags; if (flags & ImGuiColorEditFlags_NoInputs) - flags = (flags & (~ImGuiColorEditFlags__DisplayMask)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; + flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; // Context menu: display and modify options (before defaults are applied) if (!(flags & ImGuiColorEditFlags_NoOptions)) ColorEditOptionsPopup(col, flags); // Read stored options - if (!(flags & ImGuiColorEditFlags__DisplayMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DisplayMask); - if (!(flags & ImGuiColorEditFlags__DataTypeMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); - if (!(flags & ImGuiColorEditFlags__PickerMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); - if (!(flags & ImGuiColorEditFlags__InputMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputMask); - flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask)); - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check that only 1 is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_DisplayMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_); + if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_); + if (!(flags & ImGuiColorEditFlags_PickerMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_); + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; @@ -4782,11 +4891,14 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag char* p = buf; while (*p == '#' || ImCharIsBlankA(*p)) p++; - i[0] = i[1] = i[2] = i[3] = 0; + i[0] = i[1] = i[2] = 0; + i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) + int r; if (alpha) - sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + r = sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) else - sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'. } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context"); @@ -4806,7 +4918,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Store current color and open a picker g.ColorPickerRef = col_v4; OpenPopup("picker"); - SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1, style.ItemSpacing.y)); + SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(-1, style.ItemSpacing.y)); } } if (!(flags & ImGuiColorEditFlags_NoOptions)) @@ -4820,8 +4932,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag TextEx(label, label_display_end); Spacing(); } - ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; - ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); EndPopup(); @@ -4863,7 +4975,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Drag and Drop Target // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. - if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) { bool accepted_drag_drop = false; if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) @@ -4885,10 +4997,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) - window->DC.LastItemId = g.ActiveId; + g.LastItemData.ID = g.ActiveId; if (value_changed) - MarkItemEdited(window->DC.LastItemId); + MarkItemEdited(g.LastItemData.ID); return value_changed; } @@ -4941,12 +5053,12 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ColorPickerOptionsPopup(col, flags); // Read stored options - if (!(flags & ImGuiColorEditFlags__PickerMask)) - flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; - if (!(flags & ImGuiColorEditFlags__InputMask)) - flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__InputMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__InputMask; - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check that only 1 is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_PickerMask_)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected if (!(flags & ImGuiColorEditFlags_NoOptions)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); @@ -5092,7 +5204,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) { @@ -5131,9 +5243,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoInputs) == 0) { PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; - if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) { // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. @@ -5141,9 +5253,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); value_changed = true; } - if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV); - if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex); PopItemWidth(); } @@ -5210,7 +5322,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; const int vert_start_idx = draw_list->VtxBuffer.Size; draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); - draw_list->PathStroke(col_white, false, wheel_thickness); + draw_list->PathStroke(col_white, 0, wheel_thickness); const int vert_end_idx = draw_list->VtxBuffer.Size; // Paint colors over existing vertices @@ -5284,7 +5396,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) value_changed = false; if (value_changed) - MarkItemEdited(window->DC.LastItemId); + MarkItemEdited(g.LastItemData.ID); PopID(); @@ -5336,8 +5448,8 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight); - window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); } else { @@ -5346,7 +5458,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (col_source.w < 1.0f) RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); else - window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); } RenderNavHighlight(bb, id); if ((flags & ImGuiColorEditFlags_NoBorder) == 0) @@ -5373,7 +5485,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl // Tooltip if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) - ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); return pressed; } @@ -5382,18 +5494,18 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; - if ((flags & ImGuiColorEditFlags__DisplayMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DisplayMask; - if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; - if ((flags & ImGuiColorEditFlags__PickerMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; - if ((flags & ImGuiColorEditFlags__InputMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputMask; - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DataTypeMask)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check only 1 option is selected + if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_; + if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; + if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; + if ((flags & ImGuiColorEditFlags_InputMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected g.ColorEditOptions = flags; } @@ -5413,9 +5525,9 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); - ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); SameLine(); - if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags__InputMask)) + if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { if (flags & ImGuiColorEditFlags_NoAlpha) Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); @@ -5434,23 +5546,23 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) { - bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__DisplayMask); - bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_); if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; ImGuiContext& g = *GImGui; ImGuiColorEditFlags opts = g.ColorEditOptions; if (allow_opt_inputs) { - if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayRGB; - if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHSV; - if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHex; + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV; + if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex; } if (allow_opt_datatype) { if (allow_opt_inputs) Separator(); - if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; - if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float; } if (allow_opt_inputs || allow_opt_datatype) @@ -5485,7 +5597,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) { - bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_); bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) return; @@ -5504,7 +5616,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; ImVec2 backup_pos = GetCursorScreenPos(); if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup - g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); SetCursorScreenPos(backup_pos); ImVec4 previewing_ref_col; memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); @@ -5717,14 +5829,14 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); bool item_add = ItemAdd(interact_bb, id); - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; - window->DC.LastItemDisplayRect = frame_bb; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; if (!item_add) { if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); - IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } @@ -5798,7 +5910,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l { is_open = !is_open; window->DC.StateStorage->SetInt(id, is_open); - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledOpen; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; } } if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) @@ -5806,7 +5918,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger. if (selected != was_selected) //-V547 - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render const ImU32 text_col = GetColorU32(ImGuiCol_Text); @@ -5837,8 +5949,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l { const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); - RenderNavHighlight(frame_bb, id, nav_highlight_flags); } + RenderNavHighlight(frame_bb, id, nav_highlight_flags); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -5850,7 +5962,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } @@ -5892,7 +6004,7 @@ void ImGui::TreePop() if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask)) { - SetNavID(window->IDStack.back(), g.NavLayer, 0); + SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect()); NavMoveRequestCancel(); } window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; @@ -5954,14 +6066,14 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFl // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow. ImGuiContext& g = *GImGui; - ImGuiLastItemDataBackup last_item_backup; + ImGuiLastItemData last_item_backup = g.LastItemData; float button_size = g.FontSize; - float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size); - float button_y = window->DC.LastItemRect.Min.y; + float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size); + float button_y = g.LastItemData.Rect.Min.y; ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); if (CloseButton(close_button_id, ImVec2(button_x, button_y))) *p_visible = false; - last_item_backup.Restore(); + g.LastItemData = last_item_backup; } return is_open; @@ -6031,12 +6143,13 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } bool item_add; - if (flags & ImGuiSelectableFlags_Disabled) + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + if (disabled_item) { - ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus; + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; item_add = ItemAdd(bb, id); - window->DC.ItemFlags = backup_item_flags; + g.CurrentItemFlags = backup_item_flags; } else { @@ -6052,6 +6165,10 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (!item_add) return false; + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (disabled_item && !disabled_global) // Only testing this as an optimization + PushDisabled(true); + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, // which would be advantageous since most selectable are not selected. if (span_all_columns && window->DC.CurrentColumns) @@ -6064,24 +6181,31 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } - if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } - if (flags & ImGuiSelectableFlags_Disabled) - selected = false; - const bool was_selected = selected; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) + if (g.NavJustMovedToId == id) + selected = pressed = true; + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); g.NavDisableHighlight = true; - SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent); } } if (pressed) @@ -6092,7 +6216,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // In this branch, Selectable() cannot toggle the selection so this will never trigger. if (selected != was_selected) //-V547 - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) @@ -6101,23 +6225,24 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(bb.Min, bb.Max, col, false, 0.0f); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); } + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); if (span_all_columns && window->DC.CurrentColumns) PopColumnsBackground(); else if (span_all_columns && g.CurrentTable) TablePopBackgroundChannel(); - if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); - if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); // Automatically close popups - if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) CloseCurrentPopup(); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + if (disabled_item && !disabled_global) + PopDisabled(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } @@ -6199,6 +6324,7 @@ void ImGui::EndListBox() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?"); + IM_UNUSED(window); EndChildFrame(); EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label @@ -6249,8 +6375,9 @@ bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(v PopID(); } EndListBox(); + if (value_changed) - MarkItemEdited(g.CurrentWindow->DC.LastItemId); + MarkItemEdited(g.LastItemData.ID); return value_changed; } @@ -6265,7 +6392,7 @@ bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(v // Plot/Graph widgets are not very good. // Consider writing your own, or using a third-party one, see: // - ImPlot https://github.com/epezent/implot -// - others https://github.com/ocornut/imgui/wiki/Useful-Widgets +// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions //------------------------------------------------------------------------- int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size) @@ -6469,42 +6596,51 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) // - EndMainMenuBar() // - BeginMenu() // - EndMenu() +// - MenuItemEx() [Internal] // - MenuItem() //------------------------------------------------------------------------- // Helpers for internal use -void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +void ImGuiMenuColumns::Update(float spacing, bool window_reappearing) { - IM_ASSERT(count == IM_ARRAYSIZE(Pos)); - IM_UNUSED(count); - Width = NextWidth = 0.0f; - Spacing = spacing; - if (clear) - memset(NextWidths, 0, sizeof(NextWidths)); - for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) - { - if (i > 0 && NextWidths[i] > 0.0f) - Width += Spacing; - Pos[i] = IM_FLOOR(Width); - Width += NextWidths[i]; - NextWidths[i] = 0.0f; - } + if (window_reappearing) + memset(Widths, 0, sizeof(Widths)); + Spacing = (ImU16)spacing; + CalcNextTotalWidth(true); + memset(Widths, 0, sizeof(Widths)); + TotalWidth = NextTotalWidth; + NextTotalWidth = 0; } -float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) { - NextWidth = 0.0f; - NextWidths[0] = ImMax(NextWidths[0], w0); - NextWidths[1] = ImMax(NextWidths[1], w1); - NextWidths[2] = ImMax(NextWidths[2], w2); - for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) - NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); - return ImMax(Width, NextWidth); + ImU16 offset = 0; + bool want_spacing = false; + for (int i = 0; i < IM_ARRAYSIZE(Widths); i++) + { + ImU16 width = Widths[i]; + if (want_spacing && width > 0) + offset += Spacing; + want_spacing |= (width > 0); + if (update_offsets) + { + if (i == 1) { OffsetLabel = offset; } + if (i == 2) { OffsetShortcut = offset; } + if (i == 3) { OffsetMark = offset; } + } + offset += width; + } + NextTotalWidth = offset; } -float ImGuiMenuColumns::CalcExtraSpace(float avail_w) const +float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark) { - return ImMax(0.0f, avail_w - Width); + Widths[0] = ImMax(Widths[0], (ImU16)w_icon); + Widths[1] = ImMax(Widths[1], (ImU16)w_label); + Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut); + Widths[3] = ImMax(Widths[3], (ImU16)w_mark); + CalcNextTotalWidth(false); + return (float)ImMax(TotalWidth, NextTotalWidth); } // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere.. @@ -6557,21 +6693,22 @@ void ImGui::EndMenuBar() // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) const ImGuiNavLayer layer = ImGuiNavLayer_Menu; - IM_ASSERT(window->DC.NavLayerActiveMaskNext & (1 << layer)); // Sanity check + IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check FocusWindow(window); - SetNavIDWithRectRel(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); - g.NavLayer = layer; + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavDisableMouseHover = g.NavMousePosDirty = true; g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; NavMoveRequestCancel(); } } + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); IM_ASSERT(window->DC.MenuBarAppending); PopClipRect(); PopID(); - window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. + window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. g.GroupStack.back().EmitItem = false; EndGroup(); // Restore position on layer 0 window->DC.LayoutType = ImGuiLayoutType_Vertical; @@ -6579,47 +6716,67 @@ void ImGui::EndMenuBar() window->DC.MenuBarAppending = false; } -bool ImGui::BeginMainMenuBar() +// Important: calling order matters! +// FIXME: Somehow overlapping with docking tech. +// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts) +bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags) { - ImGuiContext& g = *GImGui; - ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); - ImGuiWindow* menu_bar_window = FindWindowByName("##MainMenuBar"); + IM_ASSERT(dir != ImGuiDir_None); - // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. - g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); - - // Get our rectangle at the top of the work area - if (menu_bar_window == NULL || menu_bar_window->BeginCount == 0) + ImGuiWindow* bar_window = FindWindowByName(name); + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); + if (bar_window == NULL || bar_window->BeginCount == 0) { - // Set window position - // We don't attempt to calculate our height ahead, as it depends on the per-viewport font size. - // However menu-bar will affect the minimum window size so we'll get the right height. - ImVec2 menu_bar_pos = viewport->Pos + viewport->CurrWorkOffsetMin; - ImVec2 menu_bar_size = ImVec2(viewport->Size.x - viewport->CurrWorkOffsetMin.x + viewport->CurrWorkOffsetMax.x, 1.0f); - SetNextWindowPos(menu_bar_pos); - SetNextWindowSize(menu_bar_size); + // Calculate and set window size/position + ImRect avail_rect = viewport->GetBuildWorkRect(); + ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; + ImVec2 pos = avail_rect.Min; + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) + pos[axis] = avail_rect.Max[axis] - axis_size; + ImVec2 size = avail_rect.GetSize(); + size[axis] = axis_size; + SetNextWindowPos(pos); + SetNextWindowSize(size); + + // Report our size into work area (for next frame) using actual window size + if (dir == ImGuiDir_Up || dir == ImGuiDir_Left) + viewport->BuildWorkOffsetMin[axis] += axis_size; + else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) + viewport->BuildWorkOffsetMax[axis] -= axis_size; } - // Create window + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set. PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint, however the presence of a menu-bar will give us the minimum height we want. - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; - bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint + bool is_open = Begin(name, NULL, window_flags); PopStyleVar(2); - // Report our size into work area (for next frame) using actual window size - menu_bar_window = GetCurrentWindow(); - if (menu_bar_window->BeginCount == 1) - viewport->CurrWorkOffsetMin.y += menu_bar_window->Size.y; + return is_open; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + + // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change + SetCurrentViewport(NULL, viewport); + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. + // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? + // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); - if (!is_open) - { + + if (is_open) + BeginMenuBar(); + else End(); - return false; - } - return true; //-V1020 + return is_open; } void ImGui::EndMainMenuBar() @@ -6677,6 +6834,10 @@ bool ImGui::BeginMenu(const char* label, bool enabled) // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. ImVec2 popup_pos, pos = window->DC.CursorPos; + PushID(label); + if (!enabled) + PushDisabled(); + const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Menu inside an horizontal menu bar @@ -6686,7 +6847,9 @@ bool ImGui::BeginMenu(const char* label, bool enabled) window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); float w = label_size.x; - pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups, ImVec2(w, 0.0f)); + RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } @@ -6696,14 +6859,19 @@ bool ImGui::BeginMenu(const char* label, bool enabled) // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); - float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame + float icon_w = 0.0f; // FIXME: This not currently exposed for BeginMenu() however you can call window->DC.MenuColumns.DeclColumns(w, 0, 0, 0) yourself + float checkmark_w = IM_FLOOR(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f)); - ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); - RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + RenderText(text_pos, label); + RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); } + if (!enabled) + PopDisabled(); - const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + const bool hovered = (g.HoveredId == id) && enabled; if (menuset_is_open) g.NavWindow = backed_nav_window; @@ -6730,6 +6898,8 @@ bool ImGui::BeginMenu(const char* label, bool enabled) moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] } + + // FIXME: Hovering a disabled BeginMenu or MenuItem won't close us if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) want_close = true; @@ -6773,7 +6943,8 @@ bool ImGui::BeginMenu(const char* label, bool enabled) if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None)) ClosePopupToLevel(g.BeginPopupStack.Size, true); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + PopID(); if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { @@ -6815,7 +6986,7 @@ void ImGui::EndMenu() EndPopup(); } -bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) @@ -6828,17 +6999,22 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo // 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. - ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); bool pressed; + PushID(label); + if (!enabled) + PushDisabled(true); + const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover; + const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful - // Note that in this situation we render neither the shortcut neither the selected tick mark + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. float w = label_size.x; window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); - pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + pressed = Selectable("", selected, flags, ImVec2(w, 0.0f)); PopStyleVar(); + RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } else @@ -6846,27 +7022,40 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo // Menu item inside a vertical menu // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. - float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f; - float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame - float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - pressed = Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; + float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; + float checkmark_w = IM_FLOOR(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame + float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = Selectable("", false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); if (shortcut_w > 0.0f) { - PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } if (selected) - RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); } + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + if (!enabled) + PopDisabled(); + PopID(); - IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); return pressed; } +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + return MenuItemEx(label, NULL, shortcut, selected, enabled); +} + bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) { - if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled)) { if (p_selected) *p_selected = !*p_selected; @@ -6910,7 +7099,7 @@ namespace ImGui static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); - static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections); + static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections); static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); } @@ -6922,12 +7111,17 @@ ImGuiTabBar::ImGuiTabBar() LastTabItemIdx = -1; } +static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab) +{ + return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; +} + static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; - const int a_section = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - const int b_section = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + const int a_section = TabItemGetSectionIdx(a); + const int b_section = TabItemGetSectionIdx(b); if (a_section != b_section) return a_section - b_section; return (int)(a->IndexDuringLayout - b->IndexDuringLayout); @@ -7103,11 +7297,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->IndexDuringLayout = (ImS16)tab_dst_n; // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) - int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + int curr_tab_section_n = TabItemGetSectionIdx(tab); if (tab_dst_n > 0) { ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; - int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + int prev_tab_section_n = TabItemGetSectionIdx(prev_tab); if (curr_tab_section_n == 0 && prev_tab_section_n != 0) need_sort_by_section = true; if (prev_tab_section_n == 2 && curr_tab_section_n != 2) @@ -7128,12 +7322,12 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; // Setup next selected tab - ImGuiID scroll_track_selected_tab_id = 0; + ImGuiID scroll_to_tab_id = 0; if (tab_bar->NextSelectedTabId) { tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; tab_bar->NextSelectedTabId = 0; - scroll_track_selected_tab_id = tab_bar->SelectedTabId; + scroll_to_tab_id = tab_bar->SelectedTabId; } // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot). @@ -7141,7 +7335,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { if (TabBarProcessReorder(tab_bar)) if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId) - scroll_track_selected_tab_id = tab_bar->ReorderRequestTabId; + scroll_to_tab_id = tab_bar->ReorderRequestTabId; tab_bar->ReorderRequestTabId = 0; } @@ -7149,7 +7343,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; if (tab_list_popup_button) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! - scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central // (whereas our tabs are stored as: leading, central, trailing) @@ -7169,8 +7363,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; - if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) - scroll_track_selected_tab_id = tab->ID; + if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_to_tab_id = tab->ID; // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, @@ -7179,12 +7373,13 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; - int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); curr_section_n = section_n; // Store data so we can build an array sorted by width if we need to shrink tabs down + IM_MSVC_WARNING_SUPPRESS(6385); int shrink_buffer_index = shrink_buffer_indexes[section_n]++; g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; @@ -7201,11 +7396,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Horizontal scrolling buttons // (note that TabBarScrollButtons() will alter BarRect.Max.x) if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) - if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) + if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { - scroll_track_selected_tab_id = scroll_track_selected_tab->ID; - if (!(scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button)) - tab_bar->SelectedTabId = scroll_track_selected_tab_id; + scroll_to_tab_id = scroll_and_select_tab->ID; + if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab_bar->SelectedTabId = scroll_to_tab_id; } // Shrink widths if full tabs don't fit in their allocated space @@ -7234,7 +7429,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (shrinked_width < 0.0f) continue; - int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + int section_n = TabItemGetSectionIdx(tab); sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } @@ -7269,7 +7464,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (found_selected_tab_id == false) tab_bar->SelectedTabId = 0; if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) - scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; + scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; // Lock in visible tab tab_bar->VisibleTabId = tab_bar->SelectedTabId; @@ -7277,12 +7472,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // CTRL+TAB can override visible tab temporarily if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar) - tab_bar->VisibleTabId = scroll_track_selected_tab_id = g.NavWindowingTarget->ID; + tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->ID; // Update scrolling - if (scroll_track_selected_tab_id) - if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id)) - TabBarScrollToTab(tab_bar, scroll_track_selected_tab, sections); + if (scroll_to_tab_id != 0) + TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) @@ -7363,6 +7557,9 @@ void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGu IM_ASSERT(TabBarFindTabByID(tab_bar, window->ID) == NULL); IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame) + if (!window->HasCloseButton) + tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation. + ImGuiTabItem new_tab; new_tab.ID = window->ID; new_tab.Flags = tab_flags; @@ -7412,9 +7609,13 @@ static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) return ImMax(scrolling, 0.0f); } -static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections) +// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys +static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections) { - if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); + if (tab == NULL) + return; + if (tab->Flags & ImGuiTabItemFlags_SectionMask_) return; ImGuiContext& g = *GImGui; @@ -7443,12 +7644,48 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, Im } } -void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir) +void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset) { - IM_ASSERT(dir == -1 || dir == +1); + IM_ASSERT(offset != 0); IM_ASSERT(tab_bar->ReorderRequestTabId == 0); tab_bar->ReorderRequestTabId = tab->ID; - tab_bar->ReorderRequestDir = (ImS8)dir; + tab_bar->ReorderRequestOffset = (ImS16)offset; +} + +void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) + return; + + const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; + const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); + + // Count number of contiguous tabs we are crossing over + const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1; + const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab); + int dst_idx = src_idx; + for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir) + { + // Reordered tabs must share the same section + const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i]; + if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder) + break; + if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_)) + break; + dst_idx = i; + + // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered. + const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x; + const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x; + //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2)) + break; + } + + if (dst_idx != src_idx) + TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx); } bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) @@ -7458,19 +7695,23 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) return false; //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools - int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir; + int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset; if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) return false; - // Reordered TabItem must share the same position flags than target + // Reordered tabs must share the same section + // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too) ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; if (tab2->Flags & ImGuiTabItemFlags_NoReorder) return false; - if ((tab1->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (tab2->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing))) + if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_)) return false; ImGuiTabItem item_tmp = *tab1; - *tab1 = *tab2; + ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2; + ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1; + const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset; + memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem)); *tab2 = item_tmp; if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) @@ -7665,7 +7906,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // If the user called us with *p_open == false, we early out and don't render. // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); if (p_open && !*p_open) { PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); @@ -7720,7 +7961,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, else { IM_ASSERT(tab->Window == NULL); - tab->NameOffset = (ImS16)tab_bar->TabsNames.size(); + tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); tab_bar->TabsNames.append(label, label + strlen(label) + 1); // Append name _with_ the zero-terminator. } @@ -7763,7 +8004,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; // Layout - const bool is_central_section = (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0; + const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; size.x = tab->Width; if (is_central_section) window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); @@ -7791,13 +8032,12 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Click to Select a tab ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap); - if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) + if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) tab_bar->NextSelectedTabId = id; - hovered |= (g.HoveredId == id); // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow() // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id. @@ -7819,21 +8059,22 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, else if (held && !tab_appearing && IsMouseDragging(0)) { // Drag and drop: re-order tabs + int drag_dir = 0; float drag_distance_from_edge_x = 0.0f; if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL))) { // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) { + drag_dir = -1; drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x; - if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) - TabBarQueueReorder(tab_bar, tab, -1); + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); } else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) { + drag_dir = +1; drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x; - if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) - TabBarQueueReorder(tab_bar, tab, +1); + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); } } @@ -7842,11 +8083,9 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, { // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id); - if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift) { float threshold_base = g.FontSize; - //float threshold_base = g.IO.ConfigDockingWithShift ? g.FontSize * 0.5f : g.FontSize; float threshold_x = (threshold_base * 2.2f); float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f); //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG] @@ -7854,19 +8093,21 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y); if (distance_from_edge_y >= threshold_y) undocking_tab = true; - else if (drag_distance_from_edge_x > threshold_x) - if ((tab_bar->ReorderRequestDir < 0 && tab_bar->GetTabOrder(tab) == 0) || (tab_bar->ReorderRequestDir > 0 && tab_bar->GetTabOrder(tab) == tab_bar->Tabs.Size - 1)) + if (drag_distance_from_edge_x > threshold_x) + if ((drag_dir < 0 && tab_bar->GetTabOrder(tab) == 0) || (drag_dir > 0 && tab_bar->GetTabOrder(tab) == tab_bar->Tabs.Size - 1)) undocking_tab = true; } if (undocking_tab) { // Undock + // FIXME: refactor to share more code with e.g. StartMouseMovingWindow DockContextQueueUndockWindow(&g, docked_window); g.MovingWindow = docked_window; SetActiveID(g.MovingWindow->MoveId, g.MovingWindow); g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min; g.ActiveIdNoClearOnFocusLoss = true; + SetActiveIdUsingNavAndKeys(); } } } @@ -7912,8 +8153,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; - // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer) - // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores) + // Tooltip + // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) + // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) + // FIXME: This is a mess. + // FIXME: We may want disabled tab to still display the tooltip? if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered()) if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); @@ -7983,7 +8227,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); } } @@ -8011,14 +8255,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, #endif // Render text label (with clipping + alpha gradient) + unsaved marker - const char* TAB_UNSAVED_MARKER = "*"; ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); - if (flags & ImGuiTabItemFlags_UnsavedDocument) - { - text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x; - ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f)); - RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL); - } ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; // Return clipped state ignoring the close button @@ -8028,7 +8265,10 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); } - // Close Button + const float button_sz = g.FontSize; + const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x * 2.0f - button_sz), bb.Min.y); + + // Close Button & Unsaved Marker // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() // 'hovered' will be true when hovering the Tab but NOT when hovering the close button // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button @@ -8036,28 +8276,40 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, bool close_button_pressed = false; bool close_button_visible = false; if (close_button_id != 0) - if (is_contents_visible || bb.GetWidth() >= g.Style.TabMinWidthForCloseButton) + if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton)) if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) close_button_visible = true; + bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x); + if (close_button_visible) { - ImGuiLastItemDataBackup last_item_backup; - const float close_button_sz = g.FontSize; + ImGuiLastItemData last_item_backup = g.LastItemData; PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding); - if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y))) + if (CloseButton(close_button_id, button_pos)) close_button_pressed = true; PopStyleVar(); - last_item_backup.Restore(); + g.LastItemData = last_item_backup; // Close with middle mouse button if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) close_button_pressed = true; - - text_pixel_clip_bb.Max.x -= close_button_sz; + } + else if (unsaved_marker_visible) + { + const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz) + g.Style.FramePadding * 2.0f); + RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text)); } + // This is all rather complicated + // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position) // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; + if (close_button_visible || unsaved_marker_visible) + { + text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f); + text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; + ellipsis_max_x = text_pixel_clip_bb.Max.x; + } RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); #if 0 diff --git a/examples/interactive/imgui-1.81/imstb_rectpack.h b/examples/interactive/imgui-1.83/imstb_rectpack.h similarity index 98% rename from examples/interactive/imgui-1.81/imstb_rectpack.h rename to examples/interactive/imgui-1.83/imstb_rectpack.h index ff2a85df..39589521 100644 --- a/examples/interactive/imgui-1.81/imstb_rectpack.h +++ b/examples/interactive/imgui-1.83/imstb_rectpack.h @@ -34,7 +34,7 @@ // Minor features // Martins Mozeiko // github:IntellectualKitty -// +// // Bugfixes / warning fixes // Jeremy Jaussaud // Fabian Giesen @@ -441,7 +441,7 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt } } tail = tail->next; - } + } } fr.prev_link = best; @@ -602,38 +602,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ diff --git a/examples/interactive/imgui-1.81/imstb_textedit.h b/examples/interactive/imgui-1.83/imstb_textedit.h similarity index 98% rename from examples/interactive/imgui-1.81/imstb_textedit.h rename to examples/interactive/imgui-1.83/imstb_textedit.h index 76446709..2c635b27 100644 --- a/examples/interactive/imgui-1.81/imstb_textedit.h +++ b/examples/interactive/imgui-1.83/imstb_textedit.h @@ -1,5 +1,5 @@ // [DEAR IMGUI] -// This is a slightly modified version of stb_textedit.h 1.13. +// This is a slightly modified version of stb_textedit.h 1.13. // Those changes would need to be pushed into nothings/stb: // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) // Grep for [DEAR IMGUI] to find the changes. @@ -19,7 +19,7 @@ // texts, as its performance does not scale and it has limited undo). // // Non-trivial behaviors are modelled after Windows text controls. -// +// // // LICENSE // @@ -217,20 +217,20 @@ // call this with the mouse x,y on a mouse down; it will update the cursor // and reset the selection start/end to the cursor point. the x,y must // be relative to the text widget, with (0,0) being the top left. -// +// // drag: // call this with the mouse x,y on a mouse drag/up; it will update the // cursor and the selection end point -// +// // cut: // call this to delete the current selection; returns true if there was // one. you should FIRST copy the current selection to the system paste buffer. // (To copy, just copy the current selection out of the string yourself.) -// +// // paste: // call this to paste text at the current cursor point or over the current // selection if there is one. -// +// // key: // call this for keyboard inputs sent to the textfield. you can use it // for "key down" events or for "translated" key events. if you need to @@ -241,7 +241,7 @@ // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to // anything other type you wante before including. // -// +// // When rendering, you can read the cursor position and selection state from // the STB_TexteditState. // @@ -716,9 +716,11 @@ static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditSta state->has_preferred_x = 0; return 1; } - // remove the undo since we didn't actually insert the characters - if (state->undostate.undo_point) - --state->undostate.undo_point; + // [DEAR IMGUI] + //// remove the undo since we didn't actually insert the characters + //if (state->undostate.undo_point) + // --state->undostate.undo_point; + // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) return 0; } @@ -764,7 +766,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->insert_mode = !state->insert_mode; break; #endif - + case STB_TEXTEDIT_K_UNDO: stb_text_undo(str, state); state->has_preferred_x = 0; @@ -779,7 +781,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, // if currently there's a selection, move cursor to start of selection if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_first(state); - else + else if (state->cursor > 0) --state->cursor; state->has_preferred_x = 0; @@ -828,7 +830,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, #ifdef STB_TEXTEDIT_MOVEWORDRIGHT case STB_TEXTEDIT_K_WORDRIGHT: - if (STB_TEXT_HAS_SELECTION(state)) + if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_last(str, state); else { state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); @@ -922,7 +924,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } break; } - + case STB_TEXTEDIT_K_UP: case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_PGUP: @@ -1014,7 +1016,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2: #endif @@ -1031,7 +1033,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->select_start = state->select_end = 0; state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -1410,38 +1412,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ diff --git a/examples/interactive/imgui-1.81/imstb_truetype.h b/examples/interactive/imgui-1.83/imstb_truetype.h similarity index 99% rename from examples/interactive/imgui-1.81/imstb_truetype.h rename to examples/interactive/imgui-1.83/imstb_truetype.h index fc815d74..48c20261 100644 --- a/examples/interactive/imgui-1.81/imstb_truetype.h +++ b/examples/interactive/imgui-1.83/imstb_truetype.h @@ -51,7 +51,7 @@ // Rob Loach Cort Stratton // Kenney Phillis Jr. github:oyvindjam // Brian Costabile github:vassvik -// +// // VERSION HISTORY // // 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() @@ -212,7 +212,7 @@ // // Advancing for the next character: // Call GlyphHMetrics, and compute 'current_point += SF * advance'. -// +// // // ADVANCED USAGE // @@ -257,7 +257,7 @@ // Curve tessellation 120 LOC \__ 550 LOC Bitmap creation // Bitmap management 100 LOC / // Baked bitmap interface 70 LOC / -// Font name matching & access 150 LOC ---- 150 +// Font name matching & access 150 LOC ---- 150 // C runtime library abstraction 60 LOC ---- 60 // // @@ -350,7 +350,7 @@ int main(int argc, char **argv) } return 0; } -#endif +#endif // // Output: // @@ -364,9 +364,9 @@ int main(int argc, char **argv) // :@@. M@M // @@@o@@@@ // :M@@V:@@. -// +// ////////////////////////////////////////////////////////////////////////////// -// +// // Complete program: print "Hello World!" banner, with bugs // #if 0 @@ -667,7 +667,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, cons // Calling these functions in sequence is roughly equivalent to calling // stbtt_PackFontRanges(). If you more control over the packing of multiple // fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version +// at the source to of stbtt_PackFontRanges() and create a custom version // using these functions, e.g. call GatherRects multiple times, // building up a single array of rects, then call PackRects once, // then call RenderIntoRects repeatedly. This may result in a @@ -975,7 +975,7 @@ STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, floa // and computing from that can allow drop-out prevention). // // The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. +// if computing lots of characters or very large sizes. @@ -1732,7 +1732,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s if (i != 0) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - // now start the new one + // now start the new one start_off = !(flags & 1); if (start_off) { // if we start off with an off-curve point, then when we need to find a point on the curve @@ -1785,7 +1785,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s int comp_num_verts = 0, i; stbtt_vertex *comp_verts = 0, *tmp = 0; float mtx[6] = {1,0,0,1,0,0}, m, n; - + flags = ttSHORT(comp); comp+=2; gidx = ttSHORT(comp); comp+=2; @@ -1815,7 +1815,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; } - + // Find transformation scales. m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); @@ -2746,7 +2746,7 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); STBTT_assert(z != NULL); if (!z) return z; - + // round dx down to avoid overshooting if (dxdy < 0) z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); @@ -2824,7 +2824,7 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac } } } - + e = e->next; } } @@ -3554,7 +3554,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info { int ix0,iy0,ix1,iy1; stbtt__bitmap gbm; - stbtt_vertex *vertices; + stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); if (scale_x == 0) scale_x = scale_y; @@ -3577,7 +3577,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info if (height) *height = gbm.h; if (xoff ) *xoff = ix0; if (yoff ) *yoff = iy0; - + if (gbm.w && gbm.h) { gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); if (gbm.pixels) { @@ -3588,7 +3588,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info } STBTT_free(vertices, info->userdata); return gbm.pixels; -} +} STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) { @@ -3600,7 +3600,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigne int ix0,iy0; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; + stbtt__bitmap gbm; stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); gbm.pixels = output; @@ -3622,7 +3622,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char * STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} +} STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) { @@ -3637,7 +3637,7 @@ STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, uns STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} +} STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) { @@ -3762,7 +3762,7 @@ static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *no con->y = 0; con->bottom_y = 0; STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); + STBTT__NOTUSED(num_nodes); } static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) @@ -4147,7 +4147,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char n = 0; for (i=0; i < num_ranges; ++i) n += ranges[i].num_chars; - + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); if (rects == NULL) return 0; @@ -4158,7 +4158,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); stbtt_PackFontRangesPackRects(spc, rects, n); - + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); STBTT_free(rects, spc->user_allocator_context); @@ -4319,7 +4319,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) + if (x_inter < x) winding += (y0 < y1) ? 1 : -1; } } @@ -4345,7 +4345,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex y1 = (int)verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) + if (x_inter < x) winding += (y0 < y1) ? 1 : -1; } } else { @@ -4357,7 +4357,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex if (hits[1][0] < 0) winding += (hits[1][1] < 0 ? -1 : 1); } - } + } } } return winding; @@ -4438,7 +4438,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc // invert for y-downwards bitmaps scale_y = -scale_y; - + { int x,y,i,j; float *precompute; @@ -4587,7 +4587,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc STBTT_free(verts, info->userdata); } return data; -} +} STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) { @@ -4605,7 +4605,7 @@ STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) // // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) { stbtt_int32 i=0; @@ -4644,7 +4644,7 @@ static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, s return i; } -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) { return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); } @@ -4773,7 +4773,7 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) { - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); } STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) @@ -4866,38 +4866,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ diff --git a/examples/interactive/imgui-1.81/misc/README.txt b/examples/interactive/imgui-1.83/misc/README.txt similarity index 76% rename from examples/interactive/imgui-1.81/misc/README.txt rename to examples/interactive/imgui-1.83/misc/README.txt index 86900089..b4ce89f0 100644 --- a/examples/interactive/imgui-1.81/misc/README.txt +++ b/examples/interactive/imgui-1.83/misc/README.txt @@ -3,6 +3,10 @@ misc/cpp/ InputText() wrappers for C++ standard library (STL) type: std::string. This is also an example of how you may wrap your own similar types. +misc/debuggers/ + Helper files for popular debuggers. + With the .natvis file, types like ImVector<> will be displayed nicely in Visual Studio debugger. + misc/fonts/ Fonts loading/merging instructions (e.g. How to handle glyph ranges, how to merge icons fonts). Command line tool "binary_to_compressed_c" to create compressed arrays to embed data in source code. @@ -12,11 +16,6 @@ misc/freetype/ Font atlas builder/rasterizer using FreeType instead of stb_truetype. Benefit from better FreeType rasterization, in particular for small fonts. -misc/natvis/ - Natvis file to describe dear imgui types in the Visual Studio debugger. - With this, types like ImVector<> will be displayed nicely in the debugger. - You can include this file a Visual Studio project file, or install it in Visual Studio folder. - misc/single_file/ Single-file header stub. We use this to validate compiling all *.cpp files in a same compilation unit. diff --git a/examples/interactive/imgui-1.81/misc/cpp/README.txt b/examples/interactive/imgui-1.83/misc/cpp/README.txt similarity index 81% rename from examples/interactive/imgui-1.81/misc/cpp/README.txt rename to examples/interactive/imgui-1.83/misc/cpp/README.txt index 8d5982e0..42915902 100644 --- a/examples/interactive/imgui-1.81/misc/cpp/README.txt +++ b/examples/interactive/imgui-1.83/misc/cpp/README.txt @@ -8,3 +8,6 @@ imgui_scoped.h Additional header file with some RAII-style wrappers for common Dear ImGui functions. Try by merging: https://github.com/ocornut/imgui/pull/2197 Discuss at: https://github.com/ocornut/imgui/issues/2096 + +See more C++ related extension on Wiki + https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness diff --git a/examples/interactive/imgui-1.81/misc/cpp/imgui_stdlib.cpp b/examples/interactive/imgui-1.83/misc/cpp/imgui_stdlib.cpp similarity index 100% rename from examples/interactive/imgui-1.81/misc/cpp/imgui_stdlib.cpp rename to examples/interactive/imgui-1.83/misc/cpp/imgui_stdlib.cpp diff --git a/examples/interactive/imgui-1.81/misc/cpp/imgui_stdlib.h b/examples/interactive/imgui-1.83/misc/cpp/imgui_stdlib.h similarity index 100% rename from examples/interactive/imgui-1.81/misc/cpp/imgui_stdlib.h rename to examples/interactive/imgui-1.83/misc/cpp/imgui_stdlib.h diff --git a/examples/interactive/imgui-1.83/misc/debuggers/README.txt b/examples/interactive/imgui-1.83/misc/debuggers/README.txt new file mode 100644 index 00000000..3f4ba83e --- /dev/null +++ b/examples/interactive/imgui-1.83/misc/debuggers/README.txt @@ -0,0 +1,16 @@ + +HELPER FILES FOR POPULAR DEBUGGERS + +imgui.gdb + GDB: disable stepping into trivial functions. + (read comments inside file for details) + +imgui.natstepfilter + Visual Studio Debugger: disable stepping into trivial functions. + (read comments inside file for details) + +imgui.natvis + Visual Studio Debugger: describe Dear ImGui types for better display. + With this, types like ImVector<> will be displayed nicely in the debugger. + (read comments inside file for details) + diff --git a/examples/interactive/imgui-1.83/misc/debuggers/imgui.gdb b/examples/interactive/imgui-1.83/misc/debuggers/imgui.gdb new file mode 100644 index 00000000..000ff6eb --- /dev/null +++ b/examples/interactive/imgui-1.83/misc/debuggers/imgui.gdb @@ -0,0 +1,12 @@ +# GDB configuration to aid debugging experience + +# To enable these customizations edit $HOME/.gdbinit (or ./.gdbinit if local gdbinit is enabled) and add: +# add-auto-load-safe-path /path/to/imgui.gdb +# source /path/to/imgui.gdb +# +# More Information at: +# * https://sourceware.org/gdb/current/onlinedocs/gdb/gdbinit-man.html +# * https://sourceware.org/gdb/current/onlinedocs/gdb/Init-File-in-the-Current-Directory.html#Init-File-in-the-Current-Directory + +# Disable stepping into trivial functions +skip -rfunction Im(Vec2|Vec4|Strv|Vector|Span)::.+ diff --git a/examples/interactive/imgui-1.83/misc/debuggers/imgui.natstepfilter b/examples/interactive/imgui-1.83/misc/debuggers/imgui.natstepfilter new file mode 100644 index 00000000..efd1957b --- /dev/null +++ b/examples/interactive/imgui-1.83/misc/debuggers/imgui.natstepfilter @@ -0,0 +1,30 @@ + + + + + + + + (ImVec2|ImVec4|ImStrv)::.+ + NoStepInto + + + (ImVector|ImSpan).*::operator.+ + NoStepInto + + + diff --git a/examples/interactive/imgui-1.81/misc/natvis/imgui.natvis b/examples/interactive/imgui-1.83/misc/debuggers/imgui.natvis similarity index 74% rename from examples/interactive/imgui-1.81/misc/natvis/imgui.natvis rename to examples/interactive/imgui-1.83/misc/debuggers/imgui.natvis index 13809776..94d17a8f 100644 --- a/examples/interactive/imgui-1.81/misc/natvis/imgui.natvis +++ b/examples/interactive/imgui-1.83/misc/debuggers/imgui.natvis @@ -1,6 +1,15 @@ + +To enable: +* include file in your VS project (most recommended: not intrusive and always kept up to date!) +* or copy in %USERPROFILE%\Documents\Visual Studio XXXX\Visualizers (current user) +* or copy in %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers (all users) + +More information at: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019 +--> @@ -13,7 +22,7 @@ - + {{Size={DataEnd-Data} }} @@ -49,5 +58,5 @@ {{ID {ID,x} Pos=({Pos.x,g} {Pos.y,g}) Size=({Size.x,g} {Size.y,g}) Parent {(ParentNode==0)?0:ParentNode->ID,x} Childs {(ChildNodes[0] != 0)+(ChildNodes[1] != 0)} Windows {Windows.Size} } - - \ No newline at end of file + + diff --git a/examples/interactive/imgui-1.81/misc/fonts/Cousine-Regular.ttf b/examples/interactive/imgui-1.83/misc/fonts/Cousine-Regular.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/Cousine-Regular.ttf rename to examples/interactive/imgui-1.83/misc/fonts/Cousine-Regular.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/DroidSans.ttf b/examples/interactive/imgui-1.83/misc/fonts/DroidSans.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/DroidSans.ttf rename to examples/interactive/imgui-1.83/misc/fonts/DroidSans.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/Karla-Regular.ttf b/examples/interactive/imgui-1.83/misc/fonts/Karla-Regular.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/Karla-Regular.ttf rename to examples/interactive/imgui-1.83/misc/fonts/Karla-Regular.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/ProggyClean.ttf b/examples/interactive/imgui-1.83/misc/fonts/ProggyClean.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/ProggyClean.ttf rename to examples/interactive/imgui-1.83/misc/fonts/ProggyClean.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/ProggyTiny.ttf b/examples/interactive/imgui-1.83/misc/fonts/ProggyTiny.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/ProggyTiny.ttf rename to examples/interactive/imgui-1.83/misc/fonts/ProggyTiny.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/Roboto-Medium.ttf b/examples/interactive/imgui-1.83/misc/fonts/Roboto-Medium.ttf similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/Roboto-Medium.ttf rename to examples/interactive/imgui-1.83/misc/fonts/Roboto-Medium.ttf diff --git a/examples/interactive/imgui-1.81/misc/fonts/binary_to_compressed_c.cpp b/examples/interactive/imgui-1.83/misc/fonts/binary_to_compressed_c.cpp similarity index 100% rename from examples/interactive/imgui-1.81/misc/fonts/binary_to_compressed_c.cpp rename to examples/interactive/imgui-1.83/misc/fonts/binary_to_compressed_c.cpp diff --git a/examples/interactive/imgui-1.81/misc/freetype/README.md b/examples/interactive/imgui-1.83/misc/freetype/README.md similarity index 73% rename from examples/interactive/imgui-1.81/misc/freetype/README.md rename to examples/interactive/imgui-1.83/misc/freetype/README.md index a3db3534..f7d7bab7 100644 --- a/examples/interactive/imgui-1.81/misc/freetype/README.md +++ b/examples/interactive/imgui-1.83/misc/freetype/README.md @@ -5,7 +5,7 @@ Build font atlases using FreeType instead of stb_truetype (which is the default ### Usage -1. Get latest FreeType binaries or build yourself (under Windows you may use vcpkg with `vcpkg install freetype`, `vcpkg integrate install`). +1. Get latest FreeType binaries or build yourself (under Windows you may use vcpkg with `vcpkg install freetype --triplet=x64-windows`, `vcpkg integrate install`). 2. Add imgui_freetype.h/cpp alongside your project files. 3. Add `#define IMGUI_ENABLE_FREETYPE` in your [imconfig.h](https://github.com/ocornut/imgui/blob/master/imconfig.h) file @@ -28,3 +28,10 @@ See https://gist.github.com/ocornut/b3a9ecf13502fd818799a452969649ad Small, thin anti-aliased fonts are typically benefiting a lots from Freetype's hinting: ![comparing_font_rasterizers](https://user-images.githubusercontent.com/8225057/107550178-fef87f00-6bd0-11eb-8d09-e2edb2f0ccfc.gif) + +### Colorful glyphs/emojis + +You can use the `ImGuiFreeTypeBuilderFlags_LoadColor` flag to load certain colorful glyphs. See +["Using Colorful Glyphs/Emojis"](https://github.com/ocornut/imgui/edit/master/docs/FONTS.md#using-colorful-glyphsemojis) section of FONTS.md. + +![colored glyphs](https://user-images.githubusercontent.com/8225057/106171241-9dc4ba80-6191-11eb-8a69-ca1467b206d1.png) diff --git a/examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.cpp b/examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.cpp similarity index 95% rename from examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.cpp rename to examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.cpp index db1b68e5..71a18870 100644 --- a/examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.cpp +++ b/examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.cpp @@ -1,11 +1,13 @@ // dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder) // (code) -// Get latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype +// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype // Original code by @vuhdo (Aleksei Skriabin). Improvements by @mikesart. Maintained since 2019 by @ocornut. // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs. +// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a prefered texture format. // 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+). // 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. // renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas(). @@ -39,7 +41,8 @@ #include FT_SYNTHESIS_H // #ifdef _MSC_VER -#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif #if defined(__GNUC__) @@ -105,7 +108,7 @@ namespace FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph. FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0. float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0. - bool IsColored; + bool IsColored; // The glyph is colored }; // Font parameters and metrics. @@ -151,12 +154,13 @@ namespace if (error != 0) return false; - memset(&Info, 0, sizeof(Info)); - SetPixelHeight((uint32_t)cfg.SizePixels); - // Convert to FreeType flags (NB: Bold and Oblique are processed separately) UserFlags = cfg.FontBuilderFlags | extra_font_builder_flags; - LoadFlags = FT_LOAD_NO_BITMAP; + + LoadFlags = 0; + if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0) + LoadFlags |= FT_LOAD_NO_BITMAP; + if (UserFlags & ImGuiFreeTypeBuilderFlags_NoHinting) LoadFlags |= FT_LOAD_NO_HINTING; if (UserFlags & ImGuiFreeTypeBuilderFlags_NoAutoHint) @@ -178,6 +182,9 @@ namespace if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor) LoadFlags |= FT_LOAD_COLOR; + memset(&Info, 0, sizeof(Info)); + SetPixelHeight((uint32_t)cfg.SizePixels); + return true; } @@ -196,7 +203,7 @@ namespace // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. // NB: FT_Set_Pixel_Sizes() doesn't seem to get us the same result. FT_Size_RequestRec req; - req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; + req.type = (UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM; req.width = 0; req.height = (uint32_t)pixel_height * 64; req.horiResolution = 0; @@ -224,7 +231,7 @@ namespace // Need an outline for this to work FT_GlyphSlot slot = Face->glyph; - IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE); + IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP); // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting) if (UserFlags & ImGuiFreeTypeBuilderFlags_Bold) @@ -594,17 +601,20 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); if (src_load_color) { - atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight * 4); - memset(atlas->TexPixelsRGBA32, 0, atlas->TexWidth * atlas->TexHeight * 4); + size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 4; + atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(tex_size); + memset(atlas->TexPixelsRGBA32, 0, tex_size); } else { - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 1; + atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(tex_size); + memset(atlas->TexPixelsAlpha8, 0, tex_size); } // 8. Copy rasterized font characters back into the main texture // 9. Setup ImFont and glyphs for runtime + bool tex_use_colors = false; for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) { ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; @@ -638,6 +648,22 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u const int tx = pack_rect.x + padding; const int ty = pack_rect.y + padding; + // Register glyph + float x0 = info.OffsetX + font_off_x; + float y0 = info.OffsetY + font_off_y; + float x1 = x0 + info.Width; + float y1 = y0 + info.Height; + float u0 = (tx) / (float)atlas->TexWidth; + float v0 = (ty) / (float)atlas->TexHeight; + float u1 = (tx + info.Width) / (float)atlas->TexWidth; + float v1 = (ty + info.Height) / (float)atlas->TexHeight; + dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX); + + ImFontGlyph* dst_glyph = &dst_font->Glyphs.back(); + IM_ASSERT(dst_glyph->Codepoint == src_glyph.Codepoint); + if (src_glyph.Info.IsColored) + dst_glyph->Colored = tex_use_colors = true; + // Blit from temporary buffer to final texture size_t blit_src_stride = (size_t)src_glyph.Info.Width; size_t blit_dst_stride = (size_t)atlas->TexWidth; @@ -656,31 +682,16 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u for (int x = 0; x < info.Width; x++) blit_dst[x] = blit_src[x]; } - - // Register glyph - float x0 = info.OffsetX + font_off_x; - float y0 = info.OffsetY + font_off_y; - float x1 = x0 + info.Width; - float y1 = y0 + info.Height; - float u0 = (tx) / (float)atlas->TexWidth; - float v0 = (ty) / (float)atlas->TexHeight; - float u1 = (tx + info.Width) / (float)atlas->TexWidth; - float v1 = (ty + info.Height) / (float)atlas->TexHeight; - dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX); - - IM_ASSERT(dst_font->Glyphs.back().Codepoint == src_glyph.Codepoint); - if (src_glyph.Info.IsColored) - dst_font->Glyphs.back().Colored = true; } src_tmp.Rects = NULL; } + atlas->TexPixelsUseColors = tex_use_colors; // Cleanup for (int buf_i = 0; buf_i < buf_bitmap_buffers.Size; buf_i++) IM_FREE(buf_bitmap_buffers[buf_i]); - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - src_tmp_array[src_i].~ImFontBuildSrcDataFT(); + src_tmp_array.clear_destruct(); ImFontAtlasBuildFinish(atlas); diff --git a/examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.h b/examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.h similarity index 95% rename from examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.h rename to examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.h index d570f83a..713e4639 100644 --- a/examples/interactive/imgui-1.81/misc/freetype/imgui_freetype.h +++ b/examples/interactive/imgui-1.83/misc/freetype/imgui_freetype.h @@ -26,7 +26,8 @@ enum ImGuiFreeTypeBuilderFlags ImGuiFreeTypeBuilderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font? ImGuiFreeTypeBuilderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? ImGuiFreeTypeBuilderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! - ImGuiFreeTypeBuilderFlags_LoadColor = 1 << 8 // Enable FreeType color-layered glyphs + ImGuiFreeTypeBuilderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs + ImGuiFreeTypeBuilderFlags_Bitmap = 1 << 9 // Enable FreeType bitmap glyphs }; namespace ImGuiFreeType diff --git a/examples/interactive/imgui-1.81/misc/single_file/imgui_single_file.h b/examples/interactive/imgui-1.83/misc/single_file/imgui_single_file.h similarity index 100% rename from examples/interactive/imgui-1.81/misc/single_file/imgui_single_file.h rename to examples/interactive/imgui-1.83/misc/single_file/imgui_single_file.h diff --git a/examples/interactive/renderer/ArcballCamera.cpp b/examples/interactive/renderer/ArcballCamera.cpp new file mode 100644 index 00000000..78cb6913 --- /dev/null +++ b/examples/interactive/renderer/ArcballCamera.cpp @@ -0,0 +1,128 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "ArcballCamera.h" + +namespace openvkl { + namespace examples { + + void ArcballCamera::fitToScreen(const box3f &box) + { + focalLength = 0.0028f; + sensorWidth = 0.0022f; + center = 0.5f * (box.lower + box.upper); + rotation = quaternionf(0.f, 0.1f * M_PI, 0.f) * + quaternionf(0.25f * M_PI, 0.f, 0.f); + + // The factor of 1.5 is empirical, but it seems to work well! + const float diag = length(box.upper-box.lower); + dollyDistance = 1.5f * diag; + } + + void ArcballCamera::rotate(const vec2f &pfrom, + const vec2f &pto, + const vec2i &res) + { + const vec2f sfrom = pixelToSensor(pfrom, res); + const vec2f sto = pixelToSensor(pto, res); + const quaternionf qfrom = sensorToArcball(sfrom); + const quaternionf qto = sensorToArcball(sto); + const quaternionf rot = qto * qfrom; + rotation = rot * rotation; + } + + // Move towards the center, or away from it. Parameters in pixels. + void ArcballCamera::dolly(const vec2f &pfrom, + const vec2f &pto, + const vec2i &res) + { + const vec2f sfrom = pixelToSensor(pfrom, res); + const vec2f sto = pixelToSensor(pto, res); + + // The minimum distance of the sensor to center is always + // focalLength; we only modify dollyDistance here. + const float delta = dot(sto - sfrom, vec2f(1.f,1.f)); + float speed = 0.5f * (dollyDistance + focalLength); + if (delta > 0) { + speed = std::max(speed, 1.f); + } + + dollyDistance = + std::max(0.1f, dollyDistance - speed * delta); + } + + void ArcballCamera::pan(const vec2f &pfrom, + const vec2f &pto, + const vec2i &res) + { + const vec3f cfrom = sensorToCamera(pixelToSensor(pfrom, res)); + const vec3f cto = sensorToCamera(pixelToSensor(pto, res)); + const vec3f delta = cameraToCenter(cto) - cameraToCenter(cfrom); + center = delta + center; + } + + void ArcballCamera::createRay(const vec2f &p, + const vec2i &res, + vec3f &o, + vec3f &d) const + { + const vec2f pSensor = pixelToSensor(p, res); + const vec3f pCamera = sensorToCamera(pSensor); + const vec3f pAperture(0.f, 0.f, -focalLength); + const vec3f dCamera = pAperture - pCamera; + const AffineSpace3f ctw = getCameraToWorld(); + o = xfmPoint(ctw, pCamera); + d = normalize(xfmVector(ctw, dCamera)); + } + + vec2f ArcballCamera::pixelToSensor(const vec2f &p, const vec2i &res) const + { + // Note: units of sensor width! + const float irx = 1.f / res.x; + return vec2f((p.x - 0.5f * res.x) * irx, -(p.y - 0.5f * res.y) * irx); + } + + vec3f ArcballCamera::sensorToCamera(const vec2f &s) const + { + return vec3f(sensorWidth * s.x, sensorWidth * s.y, 0.f); + } + + /* + * We use this method to enable panning. In particular, we project + * sensor positions to a plane defined by the center position and + * parallel to the sensor. + * This way we obtain an offset, in world coordinates, from a + * screen space delta. + */ + vec3f ArcballCamera::cameraToCenter(const vec3f &pCamera) const + { + const float proj = -dollyDistance / focalLength; + const vec3f pCenter( + proj * pCamera.x, proj * pCamera.y, dollyDistance + focalLength); + return rcp(LinearSpace3f(rotation)) * pCenter; + } + + quaternionf ArcballCamera::sensorToArcball(const vec2f &p) const + { + const float dist = dot(p, p); + // If we're on/in the sphere return the point on it + if (dist <= 1.f) { + return quaternionf(0, p.x, p.y, std::sqrt(1.f - dist)); + } else { + // otherwise we project the point onto the sphere + const vec2f unitDir = normalize(p); + return quaternionf(0, unitDir.x, unitDir.y, 0); + } + } + + // NOTE: If this becomes a performance problem, cache the matrices. + AffineSpace3f ArcballCamera::getCameraToWorld() const + { + return AffineSpace3f::translate(center) * + AffineSpace3f(rcp(LinearSpace3f(rotation))) * + AffineSpace3f::translate( + vec3f(0.f, 0.f, dollyDistance + focalLength)); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/ArcballCamera.h b/examples/interactive/renderer/ArcballCamera.h new file mode 100644 index 00000000..3490ee9f --- /dev/null +++ b/examples/interactive/renderer/ArcballCamera.h @@ -0,0 +1,125 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + /* + * The camera model is a pinhole camera with this configuration: + * + * / + * / + * S\ / + * S \ / + * | \ / + * O A ------- <-d-> ----- C + * | / \ + * S / \ + * S/ \ + * \ + * \ + * + * || + * + * The camera origin is at O, in the center of the sensor. + * f is the focal length, and the camera rotates around the + * center at C, which is at a distance d+f from the sensor. + * + * The sensor has a set width, but based on the available screen + * space may have an arbitrary aspect ratio. + * + * The sensor is parametrized in terms of its width, such that + * (0,0) is the center of the sensor, and it ranges horizontally + * from -0.5 to 0.5. + * + * To transform a point (sx,sy) in sensor coordinates to world coordinates, + * first multiply by the sensor width. Then, dolly, rotate, and move the + * center: + * + * pw = Mc * Mr * Md * sensorWidth * (sx,sy,0) + * + * Local camera directions are defined by rays through the aperture: + * + * d(sx,sy) = (0,0,-f) - (sensorWidth * (sx, sy, 0)) + * + */ + struct ArcballCamera + { + ArcballCamera() {} + + // Set parameters to fit the given box on screen. + void fitToScreen(const box3f &box); + + // Rotate around the center. Parameters in pixels. + void rotate(const vec2f &pfrom, const vec2f &pto, const vec2i &res); + + // Dolly the camera towards / away from the center. + void dolly(const vec2f &pfrom, const vec2f &pto, const vec2i &res); + + // Move the center in a plane perpendicular to the sensor. + // Parameters in pixels. + void pan(const vec2f &pfrom, const vec2f &pto, const vec2i &res); + + void createRay(const vec2f &p, + const vec2i &res, + vec3f &o, + vec3f &d) const; + + vec3f getEyePos() const + { + return xfmPoint(getCameraToWorld(), vec3f(0, 0, 0)); + } + + vec3f getCenterPos() const + { + return -center; + } + + vec3f getLookDir() const + { + return xfmVector(getCameraToWorld(), vec3f(0, 0, 1)); + } + + vec3f getUpDir() const + { + return xfmVector(getCameraToWorld(), vec3f(0, 1, 0)); + } + + float getSensorWidth() const + { + return sensorWidth; + } + + float getFocalLength() const + { + return focalLength; + } + + AffineSpace3f getCameraToWorld() const; + + private: + // Sensor space is in units of sensor width (so we divide by res.x) + // with the center of the screen (0,0). + vec2f pixelToSensor(const vec2f &p, const vec2i &res) const; + vec3f sensorToCamera(const vec2f &s) const; + vec3f cameraToCenter(const vec3f &pCamera) const; + quaternionf sensorToArcball(const vec2f &p) const; + + private: + float focalLength{0.0028f}; + float sensorWidth{0.0022f}; + float dollyDistance{1.f}; + vec3f center{0.f, 0.f, 0.f}; + quaternionf rotation{one}; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/CMakeLists.txt b/examples/interactive/renderer/CMakeLists.txt new file mode 100644 index 00000000..c4b0f685 --- /dev/null +++ b/examples/interactive/renderer/CMakeLists.txt @@ -0,0 +1,37 @@ +## Copyright 2019-2021 Intel Corporation +## SPDX-License-Identifier: Apache-2.0 + +include_directories_ispc( + ${CMAKE_SOURCE_DIR}/openvkl/include + ${CMAKE_SOURCE_DIR}/openvkl/devices/cpu/math + ${RKCOMMON_INCLUDE_DIRS} +) + + +set(VKL_EXAMPLE_RENDERERS_SRC + ArcballCamera.cpp + DensityPathTracer.cpp + DensityPathTracer.ispc + Framebuffer.cpp + Framebuffer.ispc + IntervalIteratorDebug.cpp + IntervalIteratorDebug.ispc + Renderer.ispc + HitIteratorRenderer.cpp + HitIteratorRenderer.ispc + RayMarchIteratorRenderer.cpp + RayMarchIteratorRenderer.ispc + Renderer.cpp + RendererParams.cpp + SamplerParams.cpp + Scheduler.cpp + Scene.cpp + Scene.ispc + VolumeParams.cpp +) + +openvkl_add_library_ispc(vkl_example_renderers STATIC + ${VKL_EXAMPLE_RENDERERS_SRC} +) +target_include_directories(vkl_example_renderers PUBLIC ${ISPC_TARGET_DIR}) +target_link_libraries(vkl_example_renderers PUBLIC openvkl openvkl_testing rkcommon::rkcommon) diff --git a/examples/interactive/renderer/Camera.ih b/examples/interactive/renderer/Camera.ih new file mode 100644 index 00000000..2c8c78e2 --- /dev/null +++ b/examples/interactive/renderer/Camera.ih @@ -0,0 +1,40 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Ray.ih" + +struct Camera +{ + float sensorWidth; + float focalLength; + vec3f ctw_R[3]; // Three rows of the rotation camera -> world. + vec3f ctw_t; // Translation camera -> world. +}; + +inline void Camera_createRay(const uniform Camera &camera, + const uniform vec2i &res, + const vec2f &p, + Ray &ray) +{ + // Note: units of sensor width! + const uniform float irx = rcp((uniform float)res.x); + const vec2f pSensor = + make_vec2f((p.x - 0.5f * res.x) * irx, -(p.y - 0.5f * res.y) * irx); + const vec3f pCamera = make_vec3f( + camera.sensorWidth * pSensor.x, camera.sensorWidth * pSensor.y, 0.f); + + // This is where the aperture is located, in camera coordinates. + const vec3f pAperture = make_vec3f(0.f, 0.f, -camera.focalLength); + const vec3f dCamera = pAperture - pCamera; + + ray.org = make_vec3f(dot(camera.ctw_R[0], pCamera) + camera.ctw_t.x, + dot(camera.ctw_R[1], pCamera) + camera.ctw_t.y, + dot(camera.ctw_R[2], pCamera) + camera.ctw_t.z); + ray.dir = normalize(make_vec3f(dot(camera.ctw_R[0], dCamera), + dot(camera.ctw_R[1], dCamera), + dot(camera.ctw_R[2], dCamera))); + ray.tnear = 0.f; + ray.tfar = inf; +} diff --git a/examples/interactive/renderer/CommandLine.h b/examples/interactive/renderer/CommandLine.h new file mode 100644 index 00000000..71e304cb --- /dev/null +++ b/examples/interactive/renderer/CommandLine.h @@ -0,0 +1,185 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + namespace cmd { + + inline void consume_0(std::list &list, + std::list::iterator &it) + { + it = list.erase(it); + } + + template + inline T consume_1(std::list &list, + std::list::iterator &it) + { + T result; + + std::list::iterator oit = it; + + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> result)) + throw std::runtime_error(*it + ": invalid argument " + *oit); + + // Note: We do not change the list until we know that the + // arguments are valid. + it = list.erase(it); + it = list.erase(it); + + return result; + } + + template + inline std::tuple consume_2( + std::list &list, std::list::iterator &it) + { + std::tuple result; + std::list::iterator oit = it; + + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<0>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<1>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + + // Note: We do not change the list until we know that the + // arguments are valid. + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + + return result; + } + + template + inline std::tuple consume_3( + std::list &list, std::list::iterator &it) + { + std::tuple result; + std::list::iterator oit = it; + + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<0>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<1>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<2>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + + // Note: We do not change the list until we know that the + // arguments are valid. + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + + return result; + } + + + template + inline std::tuple consume_4( + std::list &list, std::list::iterator &it) + { + std::tuple result; + std::list::iterator oit = it; + + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<0>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<1>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<2>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + { + ++oit; + if (oit == list.end()) + throw std::runtime_error(*it + ": missing arguments"); + + std::istringstream is(*oit); + if (!(is >> std::get<3>(result))) + throw std::runtime_error(*it + ": invalid argument " + *oit); + } + + // Note: We do not change the list until we know that the + // arguments are valid. + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + it = list.erase(it); + + return result; + } + + + } // namespace cmd + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/renderer/DensityPathTracer.cpp b/examples/interactive/renderer/DensityPathTracer.cpp new file mode 100644 index 00000000..c512309b --- /dev/null +++ b/examples/interactive/renderer/DensityPathTracer.cpp @@ -0,0 +1,233 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "DensityPathTracer.h" +#include "DensityPathTracer_ispc.h" + +#include +#include +#include + +namespace openvkl { + namespace examples { + + // ------------------------------------------------------------------------- + + static vec3f cartesian(const float phi, + const float sinTheta, + const float cosTheta) + { + float sinPhi = std::sin(phi); + float cosPhi = std::cos(phi); + return vec3f(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta); + } + + static vec3f uniformSampleSphere(const float radius, const vec2f &s) + { + const float phi = float(two_pi) * s.x; + const float cosTheta = radius * (1.f - 2.f * s.y); + const float sinTheta = 2.f * radius * std::sqrt(s.y * (1.f - s.y)); + return cartesian(phi, sinTheta, cosTheta); + } + + // ------------------------------------------------------------------------- + + DensityPathTracer::DensityPathTracer(Scene &scene) : ScalarRenderer{scene} + { + } + + DensityPathTracer::~DensityPathTracer() + { + scheduler.stop(*this); + } + + void DensityPathTracer::beforeFrame(bool &needToClear) + { + ScalarRenderer::beforeFrame(needToClear); + + bool paramsChanged = false; + scheduler.locked(guiParams, [&]() { + paramsChanged = params.updateIfChanged(guiParams); + }); + + needToClear |= paramsChanged; + } + + void DensityPathTracer::afterFrame() + { + ++frame; + } + + bool DensityPathTracer::sampleWoodcock(RNG &rng, + const Ray &ray, + const range1f &hits, + float &t, + float &sample, + float &transmittance) const + { + t = hits.lower; + const float sigmaMax = params->sigmaTScale; + + if (sigmaMax <= 0.f) { + transmittance = 1.f; + return false; + } + + while (true) { + vec2f randomNumbers = rng.getFloats(); + vec2f randomNumbers2 = rng.getFloats(); + + t = t + -std::log(1.f - randomNumbers.x) / sigmaMax; + + if (t > hits.upper) { + transmittance = 1.f; + return false; + } + + const vec3f c = ray.org + t * ray.dir; + float time = rendererParams->time; + if (params->motionBlur) { + time = time + (randomNumbers2.x - 0.5f) * params->shutter; + } + time = clamp(time, 0.f, 1.f); + sample = vklComputeSample(scene.volume.getSampler(), + (const vkl_vec3f *)&c, + rendererParams->attributeIndex, + time); + + vec4f sampleColorAndOpacity = sampleTransferFunction(sample); + + // sigmaT must be mono-chromatic for Woodcock sampling + const float sigmaTSample = sigmaMax * sampleColorAndOpacity.w; + + if (randomNumbers.y < sigmaTSample / sigmaMax) + break; + } + + transmittance = 0.f; + return true; + } + + vec3f DensityPathTracer::integrate(RNG &rng, + Ray &ray, + int scatterIndex, + int &maxScatterIndex) const + { + vec3f Le = vec3f(0.f); + maxScatterIndex = std::max(scatterIndex, maxScatterIndex); + + const box3f volumeBounds = scene.volume.getBounds(); + ray.t = intersectRayBox(ray.org, ray.dir, volumeBounds); + + if (!ray.t.empty()) { + float t = 0.f; + float sample = 0.f; + float transmittance = 0.f; + const bool haveEvent = + sampleWoodcock(rng, ray, ray.t, t, sample, transmittance); + + if (!haveEvent) { + if (scatterIndex > 0) { + Le += transmittance * vec3f(params->ambientLightIntensity); + } + } else if (scatterIndex < params->maxNumScatters) { + const vec3f p = ray.org + t * ray.dir; + + Ray scatteringRay; + scatteringRay.t = range1f(0.f, inf); + scatteringRay.org = p; + scatteringRay.dir = uniformSampleSphere(1.f, rng.getFloats()); + + const vec3f inscatteredLe = + integrate(rng, scatteringRay, scatterIndex + 1, maxScatterIndex); + + const vec4f sampleColorAndOpacity = sampleTransferFunction(sample); + const vec3f sigmaSSample = params->sigmaSScale * + vec3f(sampleColorAndOpacity) * + sampleColorAndOpacity.w; + Le = Le + sigmaSSample * inscatteredLe; + } + } + + return Le; + } + + void DensityPathTracer::renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const + + { + RNG rng(frame, seed); + int maxScatterIndex = 0; + vec3f color = integrate(rng, ray, 0, maxScatterIndex); + float alpha = maxScatterIndex > 0 ? 1.f : 0.f; + if (params->showBbox && !ray.t.empty()) { + const float bboxBlend = 0.2f; + alpha = (1.f - bboxBlend) * alpha + bboxBlend * 1.f; + color = (1.f - bboxBlend) * color + bboxBlend * vec3f(1.f); + } + rgba.x += color.x; + rgba.y += color.y; + rgba.z += color.z; + rgba.w += alpha; + weight += 1.f; + } + + // ------------------------------------------------------------------------- + + DensityPathTracerIspc::DensityPathTracerIspc(Scene &scene) + : IspcRenderer{scene} + { + ispcParams = ispc::DensityPathTracerParams_create(); + } + + DensityPathTracerIspc::~DensityPathTracerIspc() + { + scheduler.stop(*this); + ispc::DensityPathTracerParams_destroy(ispcParams); + ispcParams = nullptr; + } + + void DensityPathTracerIspc::beforeFrame(bool &needToClear) + { + IspcRenderer::beforeFrame(needToClear); + + scheduler.locked(guiParams, [&]() { + needToClear |= params.updateIfChanged(guiParams); + }); + + if (needToClear) { + ispc::DensityPathTracerParams_set(ispcParams, + params->shutter, + params->motionBlur, + params->sigmaTScale, + params->sigmaSScale, + params->maxNumScatters, + params->ambientLightIntensity, + params->showBbox); + } + } + + void DensityPathTracerIspc::afterFrame() + { + ++frame; + } + + void DensityPathTracerIspc::renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const + { + ispc::DensityPathTracer_renderPixel( + ispcParams, + ispcScene, + static_cast(frame), + reinterpret_cast(resolution), + offset, + reinterpret_cast(rgbas), + weights); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/DensityPathTracer.h b/examples/interactive/renderer/DensityPathTracer.h new file mode 100644 index 00000000..8a825371 --- /dev/null +++ b/examples/interactive/renderer/DensityPathTracer.h @@ -0,0 +1,92 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Random.h" +#include "Renderer.h" + +namespace openvkl { + namespace examples { + + struct DensityPathTracerParams + { + float shutter{0.5f}; + bool motionBlur{false}; + float sigmaTScale{1.f}; + float sigmaSScale{1.f}; + int maxNumScatters{1}; + float ambientLightIntensity{1.f}; + bool showBbox{false}; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class DensityPathTracer final : public ScalarRenderer + { + public: + DensityPathTracer(Scene &scene); + ~DensityPathTracer(); + void beforeFrame(bool &needToClear) override final; + void afterFrame() override final; + + Versioned &getGuiParams() { + return guiParams; + } + + protected: + void renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const override final; + + private: + bool sampleWoodcock(RNG &rng, + const Ray &ray, + const range1f &hits, + float &t, + float &sample, + float &transmittance) const; + + vec3f integrate(RNG &rng, + Ray &ray, + int scatterIndex, + int &maxScatterIndex) const; + + private: + Versioned guiParams; + Versioned params; // Used by the worker. + size_t frame{0}; // This is used for random sampling. + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class DensityPathTracerIspc final : public IspcRenderer + { + public: + DensityPathTracerIspc(Scene &scene); + ~DensityPathTracerIspc(); + void beforeFrame(bool &needToClear) override final; + void afterFrame() override final; + + Versioned &getGuiParams() { + return guiParams; + } + + protected: + void renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const override final; + + private: + Versioned guiParams; + Versioned params; // Used by the worker. + void *ispcParams{nullptr}; + size_t frame{0}; // This is used for random sampling. + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/DensityPathTracer.ispc b/examples/interactive/renderer/DensityPathTracer.ispc new file mode 100644 index 00000000..8b4ac318 --- /dev/null +++ b/examples/interactive/renderer/DensityPathTracer.ispc @@ -0,0 +1,208 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Random.ih" +#include "Ray.ih" +#include "Scene.ih" + +#include "openvkl/openvkl.isph" + +#include +#include + +struct DensityPathTracerParams +{ + float shutter; + bool motionBlur; + float sigmaTScale; + float sigmaSScale; + int maxNumScatters; + float ambientLightIntensity; + bool showBbox; +}; + +export void *uniform DensityPathTracerParams_create() +{ + DensityPathTracerParams *uniform self = uniform new DensityPathTracerParams; + memset(self, 0, sizeof(uniform DensityPathTracerParams)); + return self; +} + +export void DensityPathTracerParams_destroy(void *uniform self) +{ + DensityPathTracerParams *uniform params = + ((DensityPathTracerParams * uniform) self); + delete params; +} + +export void DensityPathTracerParams_set(void *uniform self, + uniform float shutter, + uniform bool motionBlur, + uniform float sigmaTScale, + uniform float sigmaSScale, + uniform int maxNumScatters, + uniform float ambientLightIntensity, + uniform bool showBbox) +{ + DensityPathTracerParams *uniform params = + ((DensityPathTracerParams * uniform) self); + + params->shutter = shutter; + params->motionBlur = motionBlur; + params->sigmaTScale = sigmaTScale; + params->sigmaSScale = sigmaSScale; + params->maxNumScatters = maxNumScatters; + params->ambientLightIntensity = ambientLightIntensity; + params->showBbox = showBbox; +} + +// ----------------------------------------------------------------------------- + +bool sampleWoodcock(const uniform DensityPathTracerParams ¶ms, + const uniform Scene &scene, + RandomTEA &rng, + const Ray &ray, + float &t, + float &sample, + float &transmittance) +{ + t = ray.tnear; + const float sigmaMax = params.sigmaTScale; + + if (sigmaMax <= 0.f) { + transmittance = 1.f; + return false; + } + + while (true) { + vec2f randomNumbers = RandomTEA__getFloats(&rng); + vec2f randomNumbers2 = RandomTEA__getFloats(&rng); + + t = t + -logf(1.f - randomNumbers.x) / sigmaMax; + + if (t > ray.tfar) { + transmittance = 1.f; + return false; + } + + const vec3f c = ray.org + t * ray.dir; + float time = scene.rendererParams.time; + if (params.motionBlur) { + time = time + (randomNumbers2.x - 0.5f) * params.shutter; + } + time = clamp(time, 0.f, 1.f); + sample = vklComputeSampleV(scene.sampler, + (varying vkl_vec3f * uniform) & c, + scene.rendererParams.attributeIndex, + &time); + + const vec4f sampleColorAndOpacity = + TransferFunction_sample(scene.transferFunction, sample); + + // sigmaT must be mono-chromatic for Woodcock sampling + const float sigmaTSample = sigmaMax * sampleColorAndOpacity.w; + + if (randomNumbers.y < sigmaTSample / sigmaMax) { + break; + } + } + + transmittance = 0.f; + return true; +} + +vec3f integrate(const uniform DensityPathTracerParams ¶ms, + const uniform Scene &scene, + RandomTEA &rng, + Ray &ray, + int scatterIndex, + int &maxScatterIndex) +{ + vec3f Le = make_vec3f(0.f); + maxScatterIndex = max(maxScatterIndex, scatterIndex); + + uniform vkl_box3f volumeBounds = vklGetBoundingBox(scene.volume); + const box3f *uniform bb = + (const uniform struct box3f *uniform) & volumeBounds; + intersectBox(ray, *bb, ray.tnear, ray.tfar); + + if (ray.tnear <= ray.tfar) { + float t = 0.f; + float sample = 0.f; + float transmittance = 0.f; + const bool haveEvent = + sampleWoodcock(params, scene, rng, ray, t, sample, transmittance); + if (!haveEvent) { + if (scatterIndex > 0) { + Le = Le + transmittance * make_vec3f(params.ambientLightIntensity); + } + } else if (scatterIndex < params.maxNumScatters) { + const vec3f p = ray.org + t * ray.dir; + + Ray scatteringRay; + scatteringRay.tnear = 0.f; + scatteringRay.tfar = inf; + scatteringRay.org = p; + scatteringRay.dir = uniformSampleSphere(1.f, RandomTEA__getFloats(&rng)); + + const vec3f inscatteredLe = integrate( + params, scene, rng, scatteringRay, scatterIndex + 1, maxScatterIndex); + + const vec4f sampleColorAndOpacity = + TransferFunction_sample(scene.transferFunction, sample); + const vec3f sigmaSSample = params.sigmaSScale * + make_vec3f(sampleColorAndOpacity) * + sampleColorAndOpacity.w; + Le = Le + sigmaSSample * inscatteredLe; + } + } + + return Le; +} + +// ----------------------------------------------------------------------------- + +export void DensityPathTracer_renderPixel(const void *uniform _params, + const void *uniform _scene, + uniform uint32 frame, + const uniform vec2i &resolution, + const uniform uint32 offset, + vec4f *uniform rgbas, + float *uniform weights) +{ + const uniform DensityPathTracerParams params = + *((const DensityPathTracerParams *uniform)_params); + const uniform Scene &scene = *((const Scene *uniform)_scene); + + const uint32 pixelIdx = offset + programIndex; + vec2i pixel; + if (!Scene_computePixel(scene, resolution.x, resolution.y, pixelIdx, pixel)) { + rgbas[pixelIdx] = make_vec4f(.18f, .18f, .18f, 1.f); + weights[pixelIdx] = 1.f; + return; + } + + RandomTEA rng; + RandomTEA__Constructor(&rng, frame, pixelIdx); + + const vec2f pixelf = make_vec2f(pixel.x, pixel.y); + Ray ray; + Camera_createRay(scene.camera, resolution, pixelf, ray); + + int maxScatterIndex = 0; + vec3f color = integrate(params, scene, rng, ray, 0, maxScatterIndex); + float alpha = (maxScatterIndex > 0 ? 1.f : 0.f); + + if (params.showBbox && (ray.tnear < ray.tfar)) { + const uniform float bboxBlend = 0.2f; + alpha = (1.f - bboxBlend) * alpha + bboxBlend * 1.f; + color = (1.f - bboxBlend) * color + bboxBlend * make_vec3f(1.f); + } + + const vec4f rgba = + rgbas[pixelIdx] + make_vec4f(color.x, color.y, color.z, alpha); + rgbas[pixelIdx] = rgba; + + const float weight = weights[pixelIdx] + 1.f; + weights[pixelIdx] = weight; +} diff --git a/examples/interactive/renderer/Framebuffer.cpp b/examples/interactive/renderer/Framebuffer.cpp new file mode 100644 index 00000000..8ce43777 --- /dev/null +++ b/examples/interactive/renderer/Framebuffer.cpp @@ -0,0 +1,97 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Framebuffer.h" +#include "Framebuffer_ispc.h" +#include "Renderer_ispc.h" + +#include +#include +#include + +#include +#include +#include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + // ------------------------------------------------------------------------- + + Framebuffer::Buffer::Buffer() : Scheduler::Lockable() {} + + Framebuffer::Buffer::Buffer(Buffer &&other) + { + using std::swap; + swap(w, other.w); + swap(h, other.h); + swap(bufRgba, other.bufRgba); + swap(bufWeight, other.bufWeight); + swap(stats, other.stats); + } + + Framebuffer::Buffer &Framebuffer::Buffer::operator=(Buffer &&other) + { + if (&other != this) { + using std::swap; + swap(w, other.w); + swap(h, other.h); + swap(bufRgba, other.bufRgba); + swap(bufWeight, other.bufWeight); + swap(stats, other.stats); + } + return *this; + } + + Framebuffer::Buffer::~Buffer() = default; + + void Framebuffer::Buffer::resize(size_t _w, size_t _h) + { + const size_t numPixels = _w * _h; + + bufRgba.resize(numPixels); + std::fill(bufRgba.begin(), bufRgba.end(), vec4f(0.f)); + + bufWeight.resize(numPixels); + std::fill(bufWeight.begin(), bufWeight.end(), 0.f); + + w = _w; + h = _h; + } + + void Framebuffer::Buffer::copy(const Buffer &other) + { + assert(w == other.w); + assert(h == other.h); + + const auto start = Framebuffer::Stats::Clock::now(); + bufRgba = other.bufRgba; + bufWeight = other.bufWeight; + stats = other.stats; + const auto end = Framebuffer::Stats::Clock::now(); + stats.copyTime = end - start; + } + + void Framebuffer::Buffer::tonemapInPlace() + { + const auto start = Framebuffer::Stats::Clock::now(); + + const size_t numPixels = w * h; + const size_t pixelsPerJob = ispc::Renderer_pixelsPerJob(); + const size_t numJobs = numPixels / pixelsPerJob; + rkcommon::tasking::parallel_for(numJobs, [&](size_t i) { + const size_t ib = i * pixelsPerJob; + ispc::tonemap(static_cast(ib), + static_cast(numPixels), + bufWeight.data(), + reinterpret_cast(bufRgba.data())); + }); + + const auto end = Framebuffer::Stats::Clock::now(); + stats.tonemapTime = end - start; + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Framebuffer.h b/examples/interactive/renderer/Framebuffer.h new file mode 100644 index 00000000..8398d9f3 --- /dev/null +++ b/examples/interactive/renderer/Framebuffer.h @@ -0,0 +1,107 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Scheduler.h" + +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + class Framebuffer + { + public: + using vec4f = rkcommon::math::vec4f; + using vec2i = rkcommon::math::vec2i; + + struct Stats + { + using Clock = std::chrono::high_resolution_clock; + + Clock::duration frameTime{0}; + Clock::duration renderTime{0}; + Clock::duration copyTime{0}; + Clock::duration tonemapTime{0}; + }; + + class Buffer : public Scheduler::Lockable + { + public: + Buffer(); + Buffer(const Buffer &) = delete; + Buffer& operator=(const Buffer &) = delete; + Buffer(Buffer &&other); + Buffer& operator=(Buffer &&other); + ~Buffer(); + + size_t getWidth() const { return w; } + size_t getHeight() const { return h; } + + vec4f *getRgba() { return bufRgba.data(); } + const vec4f *getRgba() const { return bufRgba.data(); } + + float *getWeight() { return bufWeight.data(); } + const float *getWeight() const { return bufWeight.data(); } + + Stats &getStats() { return stats; } + const Stats &getStats() const { return stats; } + + void tonemapInPlace(); + void copy(const Buffer &other); + + private: + friend class Framebuffer; + void resize(size_t _w, size_t _h); + + private: + size_t w{0}; + size_t h{0}; + rkcommon::containers::AlignedVector bufRgba; + rkcommon::containers::AlignedVector bufWeight; + Stats stats; + }; + + Buffer &getFrontBuffer() { + return frontBuffer; + } + const Buffer &getFrontBuffer() const { + return frontBuffer; + } + + Buffer &getBackBuffer() { + return backBuffer; + } + const Buffer &getBackBuffer() const { + return backBuffer; + } + + void resize(size_t w, size_t h) + { + backBuffer.resize(w, h); + frontBuffer.resize(w, h); + width = w; + height = h; + } + + size_t getWidth() const { + return width; + } + + size_t getHeight() const { + return height; + } + + private: + size_t width{0}; + size_t height{0}; + Buffer frontBuffer; + Buffer backBuffer; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Framebuffer.ispc b/examples/interactive/renderer/Framebuffer.ispc new file mode 100644 index 00000000..83e8a7d6 --- /dev/null +++ b/examples/interactive/renderer/Framebuffer.ispc @@ -0,0 +1,37 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include + +inline vec4f linearToSrgba(const vec4f &rgba) +{ + const uniform float gamma = 1.f / 2.2f; + return make_vec4f(powf(rgba.x * rgba.w, gamma), + powf(rgba.y * rgba.w, gamma), + powf(rgba.z * rgba.w, gamma), + 1.f); +} + +export void tonemap(uniform uint32 offset, + uniform uint32 size, + const float *uniform _weight, + vec4f *uniform _rgba) +{ + const uint32 idx = offset + programIndex; + if (idx >= size) { + return; + } + + vec4f rgba = _rgba[idx]; + const float weight = _weight[idx]; + + if (weight > 0.f) { + const float rweight = 1.f / weight; + rgba = linearToSrgba(rweight * rgba); + } else { + rgba = make_vec4f(0.f, 0.f, 0.f, 1.f); + } + + _rgba[idx] = rgba; +} + diff --git a/examples/interactive/renderer/HitIteratorRenderer.cpp b/examples/interactive/renderer/HitIteratorRenderer.cpp new file mode 100644 index 00000000..c088dcfa --- /dev/null +++ b/examples/interactive/renderer/HitIteratorRenderer.cpp @@ -0,0 +1,272 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "HitIteratorRenderer.h" +#include "HitIteratorRenderer_ispc.h" + +#include +#include + +namespace openvkl { + namespace examples { + + template + HitIteratorRendererShared::HitIteratorRendererShared( + const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler) + : scene{scene}, rendererParams{rendererParams}, scheduler{scheduler} + { + } + + template + HitIteratorRendererShared::~HitIteratorRendererShared() + { + assert(!hitContext); + } + + template + void HitIteratorRendererShared::updateHitContext() + { + vklSetInt(hitContext, "attributeIndex", rendererParams->attributeIndex); + + // if we have isovalues, set these values on the context + VKLData valuesData = nullptr; + + if (!params->isoValues.empty()) { + valuesData = vklNewData(getOpenVKLDevice(), + params->isoValues.size(), + VKL_FLOAT, + params->isoValues.data()); + } + + vklSetData(hitContext, "values", valuesData); + + if (valuesData) { + vklRelease(valuesData); + } + + vklCommit(hitContext); + } + + template + void HitIteratorRendererShared::beforeStart() + { + assert(!hitContext); + VKLSampler sampler = scene->volume.getSampler(); + hitContext = vklNewHitIteratorContext(sampler); + updateHitContext(); + } + + template + void HitIteratorRendererShared::afterStop() + { + if (hitContext) { + vklRelease(hitContext); + hitContext = nullptr; + } + } + + template + void HitIteratorRendererShared::beforeFrame(bool &needToClear) + { + bool paramsChanged = false; + scheduler->locked(guiParams, [&]() { + paramsChanged = params.updateIfChanged(guiParams); + }); + + needToClear |= paramsChanged; + + // Renderer params contain the attribute index. This is why + // we update the hit context when the parent changed. + if (needToClear) { + updateHitContext(); + } + } + + // ------------------------------------------------------------------------- + + HitIteratorRenderer::HitIteratorRenderer(Scene &scene) + : ScalarRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + } + + HitIteratorRenderer::~HitIteratorRenderer() + { + scheduler.stop(*this); // Causes hit context to be cleared. + } + + void HitIteratorRenderer::beforeStart() + { + ScalarRenderer::beforeStart(); + shared->beforeStart(); + } + + void HitIteratorRenderer::afterStop() + { + ScalarRenderer::afterStop(); + shared->afterStop(); + } + + void HitIteratorRenderer::beforeFrame(bool &needToClear) + { + ScalarRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + } + + void HitIteratorRenderer::renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const + { + const box3f volumeBounds = scene.volume.getBounds(); + ray.t = intersectRayBox(ray.org, ray.dir, volumeBounds); + + vkl_range1f tRange; + tRange.lower = ray.t.lower; + tRange.upper = ray.t.upper; + + void *hitIteratorBuffer = + alloca(vklGetHitIteratorSize(shared->hitContext)); + void *shadowHitIteratorBuffer = + alloca(vklGetHitIteratorSize(shared->hitContext)); + + VKLHitIterator iterator = vklInitHitIterator(shared->hitContext, + (vkl_vec3f *)&ray.org, + (vkl_vec3f *)&ray.dir, + &tRange, + rendererParams->time, + hitIteratorBuffer); + + VKLHit hit; + const float surfaceAlpha = 0.6f; + const float emission[] = {1.f, 0.8f}; + const vec3f lightDir[] = {normalize(vec3f(1.f, 1.f, 1.f)), + normalize(vec3f(1.f, 1.f, -1.f))}; + + vec3f color(0.f); + float alpha = 0.f; + + VKLSampler sampler = scene.volume.getSampler(); + + while (vklIterateHit(iterator, &hit) && alpha < 0.99f) { + const vec3f c = ray.org + hit.t * ray.dir; + const vkl_vec3f grad = + vklComputeGradient(sampler, + (vkl_vec3f *)&c, + rendererParams->attributeIndex, + rendererParams->time); + vec3f N = normalize(vec3f(grad.x, grad.y, grad.z)); + if (std::isnan(N.x) || std::isnan(N.y) || std::isnan(N.z)) + N = vec3f(0.f); + // wi and wo both point away from the event. + const vec3f wi = normalize(-1.f * ray.dir); + const float ci = dot(N, wi); + + float illum = 0.f; + if (length(N) > 0) { + illum = 0.1f; // Ambient term. + // Passing through the surface? Include the surface we started on in + // alpha. + for (int i = 0; i < 2; ++i) { + const vec3f wo = lightDir[i]; + const float co = dot(N, wo); + + // Only test for shadow if we don't have to go through this surface. + if ((co > 0) == (ci > 0)) { + VKLHit shadowHit; + vkl_range1f tShadowRange; + tShadowRange.lower = hit.epsilon; + tShadowRange.upper = inf; + VKLHitIterator shadowIterator = + vklInitHitIterator(shared->hitContext, + (vkl_vec3f *)&c, + (vkl_vec3f *)&wo, + &tShadowRange, + rendererParams->time, + shadowHitIteratorBuffer); + if (!vklIterateHit(shadowIterator, &shadowHit)) { + illum += abs(co) * emission[i]; // Lambertian surface shading. + } + } + } + } else { + // We have no normal, so do not attempt to shade. + illum = 1.f; + } + // Lambertian surface shading. + const vec4f surfaceColorAndOpacity = sampleTransferFunction(hit.sample); + const vec3f albedo = vec3f(surfaceColorAndOpacity); + color = color + (1 - alpha) * illum * albedo; + alpha = alpha + (1.f - alpha) * surfaceAlpha; + } + + rgba.x = color.x; + rgba.y = color.y; + rgba.z = color.z; + rgba.w = 1.f; + weight = 1.f; + } + + // ------------------------------------------------------------------------- + + HitIteratorRendererIspc::HitIteratorRendererIspc(Scene &scene) + : IspcRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + ispcParams = ispc::HitIteratorRendererParams_create(); + } + + HitIteratorRendererIspc::~HitIteratorRendererIspc() + { + scheduler.stop(*this); // Causes hit context to be cleared. + + ispc::HitIteratorRendererParams_destroy(ispcParams); + ispcParams = nullptr; + } + + void HitIteratorRendererIspc::beforeStart() + { + IspcRenderer::beforeStart(); + shared->beforeStart(); + } + + void HitIteratorRendererIspc::afterStop() + { + IspcRenderer::afterStop(); + shared->afterStop(); + } + + void HitIteratorRendererIspc::beforeFrame(bool &needToClear) + { + IspcRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + + if (needToClear) { + ispc::HitIteratorRendererParams_set( + ispcParams, + static_cast(shared->params->isoValues.size()), + shared->params->isoValues.data()); + } + } + + void HitIteratorRendererIspc::renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const + { + ispc::HitIteratorRenderer_renderPixel( + ispcParams, + ispcScene, + shared->hitContext, + reinterpret_cast(resolution), + offset, + reinterpret_cast(rgbas), + weights); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/HitIteratorRenderer.h b/examples/interactive/renderer/HitIteratorRenderer.h new file mode 100644 index 00000000..3e55bdff --- /dev/null +++ b/examples/interactive/renderer/HitIteratorRenderer.h @@ -0,0 +1,97 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "Renderer.h" + +namespace openvkl { + namespace examples { + + struct HitIteratorRendererParams + { + std::vector isoValues{-1.f, 0.f, 1.f}; + }; + + template + struct HitIteratorRendererShared + { + HitIteratorRendererShared(const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler); + ~HitIteratorRendererShared(); + + void updateHitContext(); + void beforeStart(); + void afterStop(); + void beforeFrame(bool &needToClear); + + Versioned guiParams; + Versioned params; // Used by the worker. + VKLHitIteratorContext hitContext{nullptr}; // Used by the worker. + + private: + const Scene *scene{nullptr}; + const RendererParams *rendererParams{nullptr}; + const Scheduler *scheduler{nullptr}; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class HitIteratorRenderer final : public ScalarRenderer + { + public: + HitIteratorRenderer(Scene &scene); + ~HitIteratorRenderer(); + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const override final; + + private: + using Shared = HitIteratorRendererShared; + std::unique_ptr shared; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class HitIteratorRendererIspc final : public IspcRenderer + { + public: + HitIteratorRendererIspc(Scene &scene); + ~HitIteratorRendererIspc(); + + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const override final; + + private: + using Shared = HitIteratorRendererShared; + std::unique_ptr shared; + void *ispcParams{nullptr}; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/HitIteratorRenderer.ispc b/examples/interactive/renderer/HitIteratorRenderer.ispc new file mode 100644 index 00000000..e64e5e49 --- /dev/null +++ b/examples/interactive/renderer/HitIteratorRenderer.ispc @@ -0,0 +1,158 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Ray.ih" +#include "Scene.ih" + +#include "openvkl/openvkl.isph" + +#include +#include + +struct HitIteratorRendererParams +{ + uint32 numIsoValues; + const float *isoValues; +}; + +export void *uniform HitIteratorRendererParams_create() +{ + HitIteratorRendererParams *uniform params = + uniform new HitIteratorRendererParams; + memset(params, 0, sizeof(uniform HitIteratorRendererParams)); + return params; +} + +export void HitIteratorRendererParams_destroy(void *uniform self) +{ + delete ((HitIteratorRendererParams * uniform) self); +} + +export void HitIteratorRendererParams_set(void *uniform self, + uniform uint32 numIsoValues, + const float *uniform isoValues) +{ + HitIteratorRendererParams *uniform params = + ((HitIteratorRendererParams * uniform) self); + + params->numIsoValues = numIsoValues; + params->isoValues = isoValues; +} + +// ----------------------------------------------------------------------------- + +export void HitIteratorRenderer_renderPixel(const void *uniform _params, + const void *uniform _scene, + void *uniform _hitContext, + const uniform vec2i &resolution, + const uniform uint32 offset, + vec4f *uniform rgbas, + float *uniform weights) +{ + const uniform HitIteratorRendererParams params = + *((const HitIteratorRendererParams *uniform)_params); + const uniform Scene &scene = *((const Scene *uniform)_scene); + uniform VKLHitIteratorContext hitContext = + ((uniform VKLHitIteratorContext)_hitContext); + + const uint32 pixelIdx = offset + programIndex; + vec2i pixel; + if (!Scene_computePixel(scene, resolution.x, resolution.y, pixelIdx, pixel)) { + rgbas[pixelIdx] = make_vec4f(.18f, .18f, .18f, 1.f); + weights[pixelIdx] = 1.f; + return; + } + + const vec2f pixelf = make_vec2f(pixel.x, pixel.y); + Ray ray; + Camera_createRay(scene.camera, resolution, pixelf, ray); + + uniform vkl_box3f volumeBounds = vklGetBoundingBox(scene.volume); + const box3f *uniform bb = + (const uniform struct box3f *uniform) & volumeBounds; + intersectBox(ray, *bb, ray.tnear, ray.tfar); + + vkl_range1f tRange; + tRange.lower = ray.tnear; + tRange.upper = ray.tfar; + + const float time = scene.rendererParams.time; + + void *uniform hitIteratorBuffer = alloca(vklGetHitIteratorSizeV(hitContext)); + void *uniform shadowHitIteratorBuffer = + alloca(vklGetHitIteratorSizeV(hitContext)); + + VKLHitIterator iterator = + vklInitHitIteratorV(hitContext, + (varying vkl_vec3f * uniform) & ray.org, + (varying vkl_vec3f * uniform) & ray.dir, + &tRange, + &time, + hitIteratorBuffer); + + VKLHit hit; + const float surfaceAlpha = 0.6f; + const float emission[] = {1.f, 0.8f}; + const vec3f lightDir[] = {normalize(make_vec3f(1.f, 1.f, 1.f)), + normalize(make_vec3f(1.f, 1.f, -1.f))}; + + vec3f color = make_vec3f(0.f); + float alpha = 0.f; + + while (vklIterateHitV(iterator, &hit) && alpha < 0.99f) { + const vec3f c = ray.org + hit.t * ray.dir; + const vkl_vec3f grad = + vklComputeGradientV(scene.sampler, + (varying vkl_vec3f * uniform) & c, + scene.rendererParams.attributeIndex, + &time); + vec3f N = normalize(make_vec3f(grad.x, grad.y, grad.z)); + if (isnan(N.x) || isnan(N.y) || isnan(N.z)) + N = make_vec3f(0.f); + + // wi and wo both point away from the event. + const vec3f wi = normalize(-1.f * ray.dir); + const float ci = dot(N, wi); + + float illum = 0.f; + if (length(N) > 0) { + illum = 0.1f; // Ambient term. + // Passing through the surface? Include the surface we started on in + // alpha. + for (int i = 0; i < 2; ++i) { + const vec3f wo = lightDir[i]; + const float co = dot(N, wo); + + // Only test for shadow if we don't have to go through this surface. + if ((co > 0) == (ci > 0)) { + VKLHit shadowHit; + vkl_range1f tShadowRange; + tShadowRange.lower = hit.epsilon; + tShadowRange.upper = inf; + VKLHitIterator shadowIterator = + vklInitHitIteratorV(hitContext, + (varying vkl_vec3f * uniform) & c, + (varying vkl_vec3f * uniform) & wo, + &tShadowRange, + &time, + shadowHitIteratorBuffer); + if (!vklIterateHitV(shadowIterator, &shadowHit)) { + illum += abs(co) * emission[i]; // Lambertian surface shading. + } + } + } + } else { + // We have no normal, so do not attempt to shade. + illum = 1.f; + } + // Lambertian surface shading. + const vec4f surfaceColorAndOpacity = + TransferFunction_sample(scene.transferFunction, hit.sample); + const vec3f albedo = make_vec3f(surfaceColorAndOpacity); + color = color + (1 - alpha) * illum * albedo; + alpha = alpha + (1.f - alpha) * surfaceAlpha; + } + + rgbas[pixelIdx] = make_vec4f(color.x, color.y, color.z, 1.f); + weights[pixelIdx] = 1.f; +} diff --git a/examples/interactive/renderer/IntervalIteratorDebug.cpp b/examples/interactive/renderer/IntervalIteratorDebug.cpp new file mode 100644 index 00000000..c4720415 --- /dev/null +++ b/examples/interactive/renderer/IntervalIteratorDebug.cpp @@ -0,0 +1,250 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "IntervalIteratorDebug.h" +#include "IntervalIteratorDebug_ispc.h" + +namespace openvkl { + namespace examples { + + template + IntervalIteratorDebugShared::IntervalIteratorDebugShared( + const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler) + : scene{scene}, rendererParams{rendererParams}, scheduler{scheduler} + { + } + + template + IntervalIteratorDebugShared::~IntervalIteratorDebugShared() + { + assert(!intervalContext); + } + + template + void IntervalIteratorDebugShared::updateIntervalContext() + { + vklSetInt( + intervalContext, "attributeIndex", rendererParams->attributeIndex); + + // set interval context value ranges based on transfer function positive + // opacity intervals, if we have any + VKLData valueRangesData = nullptr; + + const std::vector valueRanges = + rendererParams->transferFunction.getPositiveOpacityValueRanges(); + + if (!valueRanges.empty()) { + valueRangesData = vklNewData(getOpenVKLDevice(), + valueRanges.size(), + VKL_BOX1F, + valueRanges.data()); + } + + vklSetData(intervalContext, "valueRanges", valueRangesData); + + if (valueRangesData) { + vklRelease(valueRangesData); + } + + vklCommit(intervalContext); + } + + template + void IntervalIteratorDebugShared::beforeStart() + { + assert(!intervalContext); + VKLSampler sampler = scene->volume.getSampler(); + intervalContext = vklNewIntervalIteratorContext(sampler); + updateIntervalContext(); + } + + template + void IntervalIteratorDebugShared::afterStop() + { + if (intervalContext) { + vklRelease(intervalContext); + intervalContext = nullptr; + } + } + + template + void IntervalIteratorDebugShared::beforeFrame(bool &needToClear) + { + scheduler->locked(guiParams, [&]() { + needToClear |= params.updateIfChanged(guiParams); + }); + + // Renderer params contain the attribute index. This is why + // we update the interval context when the parent changed. + if (needToClear) { + updateIntervalContext(); + } + } + + // ------------------------------------------------------------------------- + + IntervalIteratorDebug::IntervalIteratorDebug(Scene &scene) + : ScalarRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + } + + IntervalIteratorDebug::~IntervalIteratorDebug() + { + scheduler.stop(*this); + } + + void IntervalIteratorDebug::beforeStart() + { + ScalarRenderer::beforeStart(); + shared->beforeStart(); + } + + void IntervalIteratorDebug::afterStop() + { + ScalarRenderer::afterStop(); + shared->afterStop(); + } + + void IntervalIteratorDebug::beforeFrame(bool &needToClear) + { + ScalarRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + } + + void IntervalIteratorDebug::renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const + { + const box3f volumeBounds = scene.volume.getBounds(); + ray.t = intersectRayBox(ray.org, ray.dir, volumeBounds); + + vkl_range1f tRange; + tRange.lower = ray.t.lower; + tRange.upper = ray.t.upper; + + void *intervalIteratorBuffer = + alloca(vklGetIntervalIteratorSize(shared->intervalContext)); + + VKLIntervalIterator iterator = + vklInitIntervalIterator(shared->intervalContext, + (vkl_vec3f *)&ray.org, + (vkl_vec3f *)&ray.dir, + &tRange, + rendererParams->time, + intervalIteratorBuffer); + + VKLInterval interval; + int intervalCount = 0; + + vec3f color(0.f); + float alpha = 0.f; + + const auto ¶ms = shared->params; + + while (vklIterateInterval(iterator, &interval) && alpha < 0.99f) { + intervalCount++; + + const float dt = interval.tRange.upper - interval.tRange.lower; + + const float normalizedValue = + rendererParams->transferFunction.valueRange.lower + + float(intervalCount) / params->intervalColorScale * + (rendererParams->transferFunction.valueRange.upper - + rendererParams->transferFunction.valueRange.lower); + + vec4f sampleColorAndOpacity = sampleTransferFunction(normalizedValue); + + sampleColorAndOpacity.w = params->intervalOpacity; + + float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); + + sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; + + if (params->showIntervalBorders && dt < interval.nominalDeltaT) { + sampleColorAndOpacity = vec4f(1.f); + clampedOpacity = 1.f; + } + + color = color + (1.f - alpha) * vec3f(sampleColorAndOpacity); + alpha = alpha + (1.f - alpha) * clampedOpacity; + + if (params->firstIntervalOnly) { + break; + } + } + + rgba.x = color.x; + rgba.y = color.y; + rgba.z = color.z; + rgba.w = 1.f; + weight = 1.f; + } + + // ------------------------------------------------------------------------- + + IntervalIteratorDebugIspc::IntervalIteratorDebugIspc(Scene &scene) + : IspcRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + ispcParams = ispc::IntervalIteratorDebugParams_create(); + } + + IntervalIteratorDebugIspc::~IntervalIteratorDebugIspc() + { + scheduler.stop(*this); // Causes interval context to be cleared. + + ispc::IntervalIteratorDebugParams_destroy(ispcParams); + ispcParams = nullptr; + } + + void IntervalIteratorDebugIspc::beforeStart() + { + IspcRenderer::beforeStart(); + shared->beforeStart(); + } + + void IntervalIteratorDebugIspc::afterStop() + { + IspcRenderer::afterStop(); + shared->afterStop(); + } + + void IntervalIteratorDebugIspc::beforeFrame(bool &needToClear) + { + IspcRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + + if (needToClear) { + ispc::IntervalIteratorDebugParams_set( + ispcParams, + shared->params->intervalColorScale, + shared->params->intervalOpacity, + shared->params->firstIntervalOnly, + shared->params->showIntervalBorders); + needToClear = true; + } + } + + void IntervalIteratorDebugIspc::renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const + { + ispc::IntervalIteratorDebug_renderPixel( + ispcParams, + ispcScene, + shared->intervalContext, + reinterpret_cast(resolution), + offset, + reinterpret_cast(rgbas), + weights); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/IntervalIteratorDebug.h b/examples/interactive/renderer/IntervalIteratorDebug.h new file mode 100644 index 00000000..5dfad725 --- /dev/null +++ b/examples/interactive/renderer/IntervalIteratorDebug.h @@ -0,0 +1,102 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Renderer.h" + +namespace openvkl { + namespace examples { + + struct IntervalIteratorDebugParams + { + float intervalColorScale{4.f}; + float intervalOpacity{0.25f}; + bool firstIntervalOnly{false}; + bool showIntervalBorders{false}; + }; + + template + struct IntervalIteratorDebugShared + { + IntervalIteratorDebugShared(const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler); + ~IntervalIteratorDebugShared(); + + void updateIntervalContext(); + void beforeStart(); + void afterStop(); + void beforeFrame(bool &needToClear); + + Versioned guiParams; + Versioned params; // Used by the worker. + VKLIntervalIteratorContext intervalContext{ + nullptr}; // Used by the worker. + + private: + const Scene *scene{nullptr}; + const RendererParams *rendererParams{nullptr}; + const Scheduler *scheduler{nullptr}; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class IntervalIteratorDebug final : public ScalarRenderer + { + public: + IntervalIteratorDebug(Scene &scene); + ~IntervalIteratorDebug(); + + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const override final; + + private: + using Shared = IntervalIteratorDebugShared; + std::unique_ptr shared; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class IntervalIteratorDebugIspc final : public IspcRenderer + { + public: + IntervalIteratorDebugIspc(Scene &scene); + ~IntervalIteratorDebugIspc(); + + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const override final; + + private: + using Shared = IntervalIteratorDebugShared; + std::unique_ptr shared; + void *ispcParams{nullptr}; + }; + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/renderer/IntervalIteratorDebug.ispc b/examples/interactive/renderer/IntervalIteratorDebug.ispc new file mode 100644 index 00000000..f848c495 --- /dev/null +++ b/examples/interactive/renderer/IntervalIteratorDebug.ispc @@ -0,0 +1,138 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Ray.ih" +#include "Scene.ih" + +#include "openvkl/openvkl.isph" + +#include +#include + +struct IntervalIteratorDebugParams +{ + float intervalColorScale; + float intervalOpacity; + bool firstIntervalOnly; + bool showIntervalBorders; +}; + +export void *uniform IntervalIteratorDebugParams_create() +{ + IntervalIteratorDebugParams *uniform params = + uniform new IntervalIteratorDebugParams; + memset(params, 0, sizeof(uniform IntervalIteratorDebugParams)); + return params; +} + +export void IntervalIteratorDebugParams_destroy(void *uniform self) +{ + delete ((IntervalIteratorDebugParams * uniform) self); +} + +export void IntervalIteratorDebugParams_set(void *uniform self, + uniform float intervalColorScale, + uniform float intervalOpacity, + uniform bool firstIntervalOnly, + uniform bool showIntervalBorders) +{ + IntervalIteratorDebugParams *uniform params = + ((IntervalIteratorDebugParams * uniform) self); + + params->intervalColorScale = intervalColorScale; + params->intervalOpacity = intervalOpacity; + params->firstIntervalOnly = firstIntervalOnly; + params->showIntervalBorders = showIntervalBorders; +} + +export void IntervalIteratorDebug_renderPixel(const void *uniform _params, + const void *uniform _scene, + void *uniform _intervalContext, + const uniform vec2i &resolution, + const uniform uint32 offset, + vec4f *uniform rgbas, + float *uniform weights) +{ + const uniform IntervalIteratorDebugParams params = + *((const IntervalIteratorDebugParams *uniform)_params); + const uniform Scene &scene = *((const Scene *uniform)_scene); + uniform VKLIntervalIteratorContext intervalContext = + ((uniform VKLIntervalIteratorContext)_intervalContext); + + const uint32 pixelIdx = offset + programIndex; + vec2i pixel; + if (!Scene_computePixel(scene, resolution.x, resolution.y, pixelIdx, pixel)) { + rgbas[pixelIdx] = make_vec4f(.18f, .18f, .18f, 1.f); + weights[pixelIdx] = 1.f; + return; + } + + const vec2f pixelf = make_vec2f(pixel.x, pixel.y); + Ray ray; + Camera_createRay(scene.camera, resolution, pixelf, ray); + + uniform vkl_box3f volumeBounds = vklGetBoundingBox(scene.volume); + const box3f *uniform bb = + (const uniform struct box3f *uniform) & volumeBounds; + intersectBox(ray, *bb, ray.tnear, ray.tfar); + + vkl_range1f tRange; + tRange.lower = ray.tnear; + tRange.upper = ray.tfar; + + const float time = scene.rendererParams.time; + + void *uniform intervalIteratorBuffer = + alloca(vklGetIntervalIteratorSizeV(intervalContext)); + + VKLIntervalIterator iterator = + vklInitIntervalIteratorV(intervalContext, + (varying vkl_vec3f * uniform) & ray.org, + (varying vkl_vec3f * uniform) & ray.dir, + &tRange, + &time, + intervalIteratorBuffer); + + VKLInterval interval; + + int intervalCount = 0; + vec3f color = make_vec3f(0.f); + float alpha = 0.f; + + while (vklIterateIntervalV(iterator, &interval) && alpha < 0.99f) { + ++intervalCount; + + const float dt = interval.tRange.upper - interval.tRange.lower; + + const float normalizedValue = scene.transferFunction.valueRange.lower + + (float)intervalCount / + params.intervalColorScale * + (scene.transferFunction.valueRange.upper - + scene.transferFunction.valueRange.lower); + + vec4f sampleColorAndOpacity = + TransferFunction_sample(scene.transferFunction, normalizedValue); + + sampleColorAndOpacity.w = params.intervalOpacity; + + float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); + + sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; + + if (params.showIntervalBorders && dt < interval.nominalDeltaT) { + sampleColorAndOpacity = make_vec4f(1.f); + clampedOpacity = 1.f; + } + + color = color + (1.f - alpha) * make_vec3f(sampleColorAndOpacity); + alpha = alpha + (1.f - alpha) * clampedOpacity; + + if (params.firstIntervalOnly) { + break; + } + } + + rgbas[pixelIdx] = make_vec4f(color.x, color.y, color.z, 1.f); + weights[pixelIdx] = 1.f; +} + diff --git a/examples/interactive/renderer/Random.h b/examples/interactive/renderer/Random.h new file mode 100644 index 00000000..544ac045 --- /dev/null +++ b/examples/interactive/renderer/Random.h @@ -0,0 +1,72 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rkcommon/math/box.h" +#include "rkcommon/math/vec.h" + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + +#if USE_STD_RANDOM + inline float getRandomUniform() + { + static thread_local std::minstd_rand rng; + static std::uniform_real_distribution distribution{0.f, 1.f}; + + return distribution(rng); + } + + struct RandomSTD + { + template + RandomSTD(const T &, const T &) + { + } + + vec2f getFloats() + { + return vec2f{getRandomUniform(), getRandomUniform()}; + } + }; + + using RNG = RandomSTD; +#else + + // TEA - Random numbers based on Tiny Encryption Algorithm // + + template + inline void tea8(T &v0, T &v1) + { + T sum{0}; + + for (int i = 0; i < NUM_ROUNDS; i++) { + sum += 0x9e3779b9; + v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + sum) ^ ((v1 >> 5) + 0xc8013ea4); + v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7e95761e); + } + } + + template + struct RandomTEA + { + RandomTEA(const T &idx, const T &seed) : v0(idx), v1(seed) {} + + vec2f getFloats() + { + tea8(v0, v1); + const float tofloat = 2.3283064365386962890625e-10f; // 1/2^32 + return vec2f{v0 * tofloat, v1 * tofloat}; + } + + T v0, v1; + }; + + using RNG = RandomTEA; +#endif + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Random.ih b/examples/interactive/renderer/Random.ih new file mode 100644 index 00000000..1079ba93 --- /dev/null +++ b/examples/interactive/renderer/Random.ih @@ -0,0 +1,63 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +/////////////////////////////////////////////////////////////////////////////// +// TEA - Random numbers based on Tiny Encryption Algorithm //////////////////// +/////////////////////////////////////////////////////////////////////////////// + +inline void tea8(unsigned int &_v0, unsigned int &_v1) +{ + unsigned int v0 = _v0; // Operate on registers to avoid slowdown! + unsigned int v1 = _v1; + unsigned int sum = 0; + + for (uniform int i = 0; i < 8; i++) { // just 8 instead of 32 rounds + sum += 0x9e3779b9; + v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + sum) ^ ((v1 >> 5) + 0xc8013ea4); + v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7e95761e); + } + + _v0 = v0; + _v1 = v1; +} + +struct RandomTEA +{ + unsigned int v0, v1; +}; + +inline void RandomTEA__Constructor(varying RandomTEA *uniform this, + const unsigned int idx, + const unsigned int seed) +{ + this->v0 = idx; + this->v1 = seed; +} + +inline varying vec2f RandomTEA__getFloats(varying RandomTEA *uniform this) +{ + tea8(this->v0, this->v1); + const float tofloat = 2.3283064365386962890625e-10f; // 1/2^32 + return make_vec2f(this->v0 * tofloat, this->v1 * tofloat); +} + +inline vec3f cartesian(const float phi, + const float sinTheta, + const float cosTheta) +{ + float sinPhi, cosPhi; + sincos(phi, &sinPhi, &cosPhi); + return make_vec3f(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta); +} + +inline vec3f uniformSampleSphere(const float radius, const vec2f s) +{ + const float phi = two_pi * s.x; + const float cosTheta = radius * (1.f - 2.f * s.y); + const float sinTheta = 2.f * radius * sqrt(s.y * (1.f - s.y)); + return cartesian(phi, sinTheta, cosTheta); +} diff --git a/examples/interactive/renderer/Ray.h b/examples/interactive/renderer/Ray.h new file mode 100644 index 00000000..65eaacad --- /dev/null +++ b/examples/interactive/renderer/Ray.h @@ -0,0 +1,22 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rkcommon/math/box.h" +#include "rkcommon/math/vec.h" + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + struct Ray + { + vec3f org; + vec3f dir; + range1f t; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Ray.ih b/examples/interactive/renderer/Ray.ih new file mode 100644 index 00000000..d134f9df --- /dev/null +++ b/examples/interactive/renderer/Ray.ih @@ -0,0 +1,26 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "rkcommon/math/vec.ih" +#include "rkcommon/math/box.ih" + +struct Ray +{ + vec3f org; + vec3f dir; + float tnear; + float tfar; +}; + +inline void intersectBox(const Ray &ray, + const uniform box3f &box, + float &tnear, + float &tfar) +{ + const vec3f mins = (box.lower - ray.org) * rcp_safe(ray.dir); + const vec3f maxs = (box.upper - ray.org) * rcp_safe(ray.dir); + tnear = reduce_max(make_vec4f(min(mins, maxs), ray.tnear)); + tfar = reduce_min(make_vec4f(max(mins, maxs), ray.tfar)); +} diff --git a/examples/interactive/renderer/RayMarchIteratorRenderer.cpp b/examples/interactive/renderer/RayMarchIteratorRenderer.cpp new file mode 100644 index 00000000..e6b8e796 --- /dev/null +++ b/examples/interactive/renderer/RayMarchIteratorRenderer.cpp @@ -0,0 +1,253 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "RayMarchIteratorRenderer.h" +#include "RayMarchIteratorRenderer_ispc.h" + +namespace openvkl { + namespace examples { + + // ------------------------------------------------------------------------- + + template + RayMarchIteratorRendererShared::RayMarchIteratorRendererShared( + const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler) + : scene{scene}, rendererParams{rendererParams}, scheduler{scheduler} + { + } + + template + RayMarchIteratorRendererShared::~RayMarchIteratorRendererShared() + { + assert(!intervalContext); + } + + template + void RayMarchIteratorRendererShared::updateIntervalContext() + { + vklSetInt( + intervalContext, "attributeIndex", rendererParams->attributeIndex); + + // set interval context value ranges based on transfer function positive + // opacity intervals, if we have any + VKLData valueRangesData = nullptr; + + const std::vector valueRanges = + rendererParams->transferFunction.getPositiveOpacityValueRanges(); + + if (!valueRanges.empty()) { + valueRangesData = vklNewData(getOpenVKLDevice(), + valueRanges.size(), + VKL_BOX1F, + valueRanges.data()); + } + + vklSetData(intervalContext, "valueRanges", valueRangesData); + + if (valueRangesData) { + vklRelease(valueRangesData); + } + + vklCommit(intervalContext); + } + + template + void RayMarchIteratorRendererShared::beforeStart() + { + assert(!intervalContext); + VKLSampler sampler = scene->volume.getSampler(); + intervalContext = vklNewIntervalIteratorContext(sampler); + updateIntervalContext(); + } + + template + void RayMarchIteratorRendererShared::afterStop() + { + if (intervalContext) { + vklRelease(intervalContext); + intervalContext = nullptr; + } + } + + template + void RayMarchIteratorRendererShared::beforeFrame(bool &needToClear) + { + scheduler->locked(guiParams, [&]() { + needToClear |= params.updateIfChanged(guiParams); + }); + + // Renderer params contain the attribute index. This is why + // we update the interval context when the parent changed. + if (needToClear) { + updateIntervalContext(); + } + } + + // ------------------------------------------------------------------------- + + RayMarchIteratorRenderer::RayMarchIteratorRenderer(Scene &scene) + : ScalarRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + } + + RayMarchIteratorRenderer::~RayMarchIteratorRenderer() + { + scheduler.stop(*this); // Causes interval context to be cleared. + } + + void RayMarchIteratorRenderer::beforeStart() + { + ScalarRenderer::beforeStart(); + shared->beforeStart(); + } + + void RayMarchIteratorRenderer::afterStop() + { + ScalarRenderer::afterStop(); + shared->afterStop(); + } + + void RayMarchIteratorRenderer::beforeFrame(bool &needToClear) + { + ScalarRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + } + + void RayMarchIteratorRenderer::renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const + { + const box3f volumeBounds = scene.volume.getBounds(); + ray.t = intersectRayBox(ray.org, ray.dir, volumeBounds); + + vkl_range1f tRange; + tRange.lower = ray.t.lower; + tRange.upper = ray.t.upper; + + void *intervalIteratorBuffer = + alloca(vklGetIntervalIteratorSize(shared->intervalContext)); + + VKLIntervalIterator iterator = + vklInitIntervalIterator(shared->intervalContext, + (vkl_vec3f *)&ray.org, + (vkl_vec3f *)&ray.dir, + &tRange, + rendererParams->time, + intervalIteratorBuffer); + + VKLInterval interval; + + vec3f color(0.f); + float alpha = 0.f; + + while (vklIterateInterval(iterator, &interval) && alpha < 0.99f) { + const float nominalSamplingDt = + interval.nominalDeltaT / shared->params->samplingRate; + + // initial sub interval, based on our renderer-defined sampling rate + // and the volume's nominal dt + box1f subInterval(interval.tRange.lower, + min(interval.tRange.lower + nominalSamplingDt, + interval.tRange.upper)); + + // integrate as long as we have valid sub intervals and are not + // fully opaque + while (subInterval.upper - subInterval.lower > 0.f && alpha < 0.99f) { + const float t = 0.5f * (subInterval.lower + subInterval.upper); + const float dt = subInterval.upper - subInterval.lower; + + // get volume sample + vec3f c = ray.org + t * ray.dir; + float sample = vklComputeSample(scene.volume.getSampler(), + (vkl_vec3f *)&c, + rendererParams->attributeIndex, + rendererParams->time); + + // map through transfer function + vec4f sampleColorAndOpacity = sampleTransferFunction(sample); + + // accumulate contribution + const float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); + + sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; + + color = color + (1.f - alpha) * vec3f(sampleColorAndOpacity); + alpha = alpha + (1.f - alpha) * clampedOpacity; + + // compute next sub interval + subInterval.lower = subInterval.upper; + subInterval.upper = + min(subInterval.lower + nominalSamplingDt, interval.tRange.upper); + } + } + + rgba.x = color.x; + rgba.y = color.y; + rgba.z = color.z; + rgba.w = 1.f; + weight = 1.f; + } + + // ------------------------------------------------------------------------- + + RayMarchIteratorRendererIspc::RayMarchIteratorRendererIspc(Scene &scene) + : IspcRenderer{scene}, + shared{rkcommon::make_unique( + &scene, &*rendererParams, &scheduler)} + { + ispcParams = ispc::RayMarchIteratorRendererParams_create(); + } + + RayMarchIteratorRendererIspc::~RayMarchIteratorRendererIspc() + { + scheduler.stop(*this); // Causes interval context to be cleared. + + ispc::RayMarchIteratorRendererParams_destroy(ispcParams); + ispcParams = nullptr; + } + + void RayMarchIteratorRendererIspc::beforeStart() + { + IspcRenderer::beforeStart(); + shared->beforeStart(); + } + + void RayMarchIteratorRendererIspc::afterStop() + { + IspcRenderer::afterStop(); + shared->afterStop(); + } + + void RayMarchIteratorRendererIspc::beforeFrame(bool &needToClear) + { + IspcRenderer::beforeFrame(needToClear); + shared->beforeFrame(needToClear); + + if (needToClear) { + ispc::RayMarchIteratorRendererParams_set(ispcParams, + shared->params->samplingRate); + } + } + + void RayMarchIteratorRendererIspc::renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const + { + ispc::RayMarchIteratorRenderer_renderPixel( + ispcParams, + ispcScene, + shared->intervalContext, + reinterpret_cast(resolution), + offset, + reinterpret_cast(rgbas), + weights); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/RayMarchIteratorRenderer.h b/examples/interactive/renderer/RayMarchIteratorRenderer.h new file mode 100644 index 00000000..a204fbe9 --- /dev/null +++ b/examples/interactive/renderer/RayMarchIteratorRenderer.h @@ -0,0 +1,100 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Renderer.h" + +namespace openvkl { + namespace examples { + + struct RayMarchIteratorRendererParams + { + float samplingRate{1.f}; + }; + + template + struct RayMarchIteratorRendererShared + { + RayMarchIteratorRendererShared(const Scene *scene, + const RendererParams *rendererParams, + const Scheduler *scheduler); + ~RayMarchIteratorRendererShared(); + + void updateIntervalContext(); + void beforeStart(); + void afterStop(); + void beforeFrame(bool &needToClear); + + Versioned guiParams; + Versioned params; // Used by the worker. + VKLIntervalIteratorContext intervalContext{ + nullptr}; // Used by the worker. + + private: + const Scene *scene{nullptr}; + const RendererParams *rendererParams{nullptr}; + const Scheduler *scheduler{nullptr}; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class RayMarchIteratorRenderer final : public ScalarRenderer + { + public: + RayMarchIteratorRenderer(Scene &scene); + ~RayMarchIteratorRenderer(); + + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const override final; + + private: + using Shared = RayMarchIteratorRendererShared; + std::unique_ptr shared; + }; + + // Note: This renderer stops itself in the destructor, so it should never + // call virtual functions in derived classes in the render loop. + // We use final to ensure whoever tries to derive is aware of this. + class RayMarchIteratorRendererIspc final : public IspcRenderer + { + public: + RayMarchIteratorRendererIspc(Scene &scene); + ~RayMarchIteratorRendererIspc(); + + void beforeStart() override final; + void afterStop() override final; + void beforeFrame(bool &needToClear) override final; + + Versioned &getGuiParams() { + return shared->guiParams; + } + + protected: + void renderPixelBlock(const vec2i &resolution, + uint32_t offset, + vec4f *rgbas, + float *weights) const override final; + + private: + using Shared = + RayMarchIteratorRendererShared; + std::unique_ptr shared; + void *ispcParams{nullptr}; + }; + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/renderer/RayMarchIteratorRenderer.ispc b/examples/interactive/renderer/RayMarchIteratorRenderer.ispc new file mode 100644 index 00000000..cac2d700 --- /dev/null +++ b/examples/interactive/renderer/RayMarchIteratorRenderer.ispc @@ -0,0 +1,137 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Ray.ih" +#include "Scene.ih" + +#include "openvkl/openvkl.isph" + +#include +#include + +struct RayMarchIteratorRendererParams +{ + float samplingRate; +}; + +export void *uniform RayMarchIteratorRendererParams_create() +{ + RayMarchIteratorRendererParams *uniform params = + uniform new RayMarchIteratorRendererParams; + memset(params, 0, sizeof(uniform RayMarchIteratorRendererParams)); + return params; +} + +export void RayMarchIteratorRendererParams_destroy(void *uniform self) +{ + delete ((RayMarchIteratorRendererParams * uniform) self); +} + +export void RayMarchIteratorRendererParams_set(void *uniform self, + uniform float samplingRate) +{ + RayMarchIteratorRendererParams *uniform params = + ((RayMarchIteratorRendererParams * uniform) self); + + params->samplingRate = samplingRate; +} + +export void RayMarchIteratorRenderer_renderPixel( + const void *uniform _params, + const void *uniform _scene, + void *uniform _intervalContext, + const uniform vec2i &resolution, + const uniform uint32 offset, + vec4f *uniform rgbas, + float *uniform weights) +{ + const uniform RayMarchIteratorRendererParams params = + *((const RayMarchIteratorRendererParams *uniform)_params); + const uniform Scene &scene = *((const Scene *uniform)_scene); + uniform VKLIntervalIteratorContext intervalContext = + ((uniform VKLIntervalIteratorContext)_intervalContext); + + const uint32 pixelIdx = offset + programIndex; + vec2i pixel; + if (!Scene_computePixel(scene, resolution.x, resolution.y, pixelIdx, pixel)) { + rgbas[pixelIdx] = make_vec4f(.18f, .18f, .18f, 1.f); + weights[pixelIdx] = 1.f; + return; + } + + const vec2f pixelf = make_vec2f(pixel.x, pixel.y); + Ray ray; + Camera_createRay(scene.camera, resolution, pixelf, ray); + + uniform vkl_box3f volumeBounds = vklGetBoundingBox(scene.volume); + const box3f *uniform bb = + (const uniform struct box3f *uniform) & volumeBounds; + intersectBox(ray, *bb, ray.tnear, ray.tfar); + + vkl_range1f tRange; + tRange.lower = ray.tnear; + tRange.upper = ray.tfar; + + const float time = scene.rendererParams.time; + + void *uniform intervalIteratorBuffer = + alloca(vklGetIntervalIteratorSizeV(intervalContext)); + + VKLIntervalIterator iterator = + vklInitIntervalIteratorV(intervalContext, + (varying vkl_vec3f * uniform) & ray.org, + (varying vkl_vec3f * uniform) & ray.dir, + &tRange, + &time, + intervalIteratorBuffer); + + VKLInterval interval; + + vec3f color = make_vec3f(0.f); + float alpha = 0.f; + + while (vklIterateIntervalV(iterator, &interval) && alpha < 0.99f) { + const float nominalSamplingDt = + interval.nominalDeltaT / params.samplingRate; + + // initial sub interval, based on our renderer-defined sampling rate + // and the volume's nominal dt + box1f subInterval = make_box1f( + interval.tRange.lower, + min(interval.tRange.lower + nominalSamplingDt, interval.tRange.upper)); + + // integrate as long as we have valid sub intervals and are not + // fully opaque + while (subInterval.upper - subInterval.lower > 0.f && alpha < 0.99f) { + const float t = 0.5f * (subInterval.lower + subInterval.upper); + const float dt = subInterval.upper - subInterval.lower; + + // get volume sample + vec3f c = ray.org + t * ray.dir; + float sample = vklComputeSampleV(scene.sampler, + (varying vkl_vec3f * uniform) & c, + scene.rendererParams.attributeIndex, + &time); + + // map through transfer function + vec4f sampleColorAndOpacity = + TransferFunction_sample(scene.transferFunction, sample); + + // accumulate contribution + const float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); + + sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; + + color = color + (1.f - alpha) * make_vec3f(sampleColorAndOpacity); + alpha = alpha + (1.f - alpha) * clampedOpacity; + + // compute next sub interval + subInterval.lower = subInterval.upper; + subInterval.upper = + min(subInterval.lower + nominalSamplingDt, interval.tRange.upper); + } + } + + rgbas[pixelIdx] = make_vec4f(color.x, color.y, color.z, 1.f); + weights[pixelIdx] = 1.f; +} diff --git a/examples/interactive/renderer/Renderer.cpp b/examples/interactive/renderer/Renderer.cpp new file mode 100644 index 00000000..ca318878 --- /dev/null +++ b/examples/interactive/renderer/Renderer.cpp @@ -0,0 +1,309 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Renderer.h" + +#include "Renderer_ispc.h" +#include "Scene_ispc.h" + +#include +#include +#include + +namespace openvkl { + namespace examples { + + Renderer::Renderer(Scene &scene) : scene(scene), scheduler(scene.scheduler) + { + // Note: + // Do not start the renderer here - the render functions don't exist + // until the derived classes have been constructed. + assert(!run.load()); + } + + Renderer::~Renderer() + { + // Note: + // Do not stop the renderer here - the render functions don't exist + // any more. + assert(!run.load()); + } + + void Renderer::beforeFrame(bool &needToClear) + { + bool cameraChanged = false; + scheduler.locked(scene.camera, [&]() { + cameraChanged = cam.updateIfChanged(scene.camera); + }); + + bool rendererParamsChanged = false; + scheduler.locked(scene.rendererParams, [&]() { + rendererParamsChanged = + rendererParams.updateIfChanged(scene.rendererParams); + }); + + needToClear |= cameraChanged || rendererParamsChanged; + } + + vec4f Renderer::sampleTransferFunction(float value) const + { + vec4f colorAndOpacity{0.f}; + + const auto &valueRange = rendererParams->transferFunction.valueRange; + const auto &colorsAndOpacities = + rendererParams->transferFunction.colorsAndOpacities; + + if (std::isnan(value) || colorsAndOpacities.empty()) { + return colorAndOpacity; + } + + if (value <= valueRange.lower) { + return colorsAndOpacities[0]; + } + + if (value >= valueRange.upper) { + return colorsAndOpacities.back(); + } + + // map the value into the range [0, size - 1] + value = (value - valueRange.lower) / + (valueRange.upper - valueRange.lower) * + (colorsAndOpacities.size() - 1); + + // index and fractional offset + const int index = floor(value); + const float remainder = value - index; + + // the final interpolated value + return ((1.f - remainder) * colorsAndOpacities[index] + + remainder * colorsAndOpacities[min( + index + 1, int(colorsAndOpacities.size() - 1))]); + } + + // ------------------------------------------------------------------------- + + HostRenderer::HostRenderer(Scene &scene) : Renderer{scene} + { + } + + HostRenderer::~HostRenderer() + { + } + + const Framebuffer &HostRenderer::getFramebuffer(size_t w, size_t h) + { + // Note: This is all in the main thread, so no need to lock any parameters + // here. + size_t width = w; + size_t height = h; + + const auto ¶ms = *(scene.rendererParams); + if (params.fixedFramebufferSize) { + width = params.framebufferSize.x; + height = params.framebufferSize.y; + } + + const bool resolutionChanged = (framebuffer.getWidth() != width || + framebuffer.getHeight() != height); + + if (resolutionChanged) { + scheduler.stop(*this); + framebuffer.resize(width, height); + scheduler.start(*this); + } + + return framebuffer; + } + + // ------------------------------------------------------------------------- + + ScalarRenderer::ScalarRenderer(Scene &scene) : HostRenderer{scene} {} + + void ScalarRenderer::renderFrameImpl(bool clearFramebuffer) + { + const auto startFrame = Framebuffer::Stats::Clock::now(); + + if (scheduler.workerMustTerminate(*this)) { + return; + } + + auto &bBuf = framebuffer.getBackBuffer(); + auto &fBuf = framebuffer.getFrontBuffer(); + + scheduler.locked(bBuf, [&]() { + const size_t ww = bBuf.getWidth(); + const size_t hh = bBuf.getHeight(); + + if (ww == 0 || hh == 0) { + return; + } + + const auto startRender = Framebuffer::Stats::Clock::now(); + rkcommon::tasking::parallel_in_blocks_of<16>( + ww * hh, [&](size_t ib, size_t ie) { + if (scheduler.workerMustTerminate(*this)) { + return; + } + + for (size_t idx = ib; idx < ie; ++idx) { + const int y = idx / ww; + const int x = idx % ww; + vec4f &rgba = bBuf.getRgba()[idx]; + float &weight = bBuf.getWeight()[idx]; + + if (rendererParams->fixedFramebufferSize && + rendererParams->restrictPixelRange) { + // The output is mirrored! + if ((hh - y - 1) < rendererParams->pixelRange.lower.y || + (hh - y - 1) >= rendererParams->pixelRange.upper.y || + (ww - x - 1) < rendererParams->pixelRange.lower.x || + (ww - x - 1) >= rendererParams->pixelRange.upper.x) { + rgba = vec4f(.18f, .18f, .18f, 1.f); + weight = 1.f; + continue; + } + } + + if (clearFramebuffer) { + rgba = vec4f(0.f); + weight = 0.f; + } + + Ray ray; + // NOTE: Should use filtered IS at some point. + cam->createRay(vec2f(x, y), vec2i(ww, hh), ray.org, ray.dir); + const size_t seed = ww * static_cast(y) + x; + renderPixel(seed, ray, rgba, weight); + } + }); + const auto endRender = Framebuffer::Stats::Clock::now(); + + // Copy output into the front buffer and prepare for display! + scheduler.locked(fBuf, [&]() { + fBuf.copy(bBuf); + fBuf.tonemapInPlace(); + fBuf.getStats().renderTime = endRender - startRender; + const auto endFrame = Framebuffer::Stats::Clock::now(); + fBuf.getStats().frameTime = endFrame - startFrame; + }); + }); + } + + // ------------------------------------------------------------------------- + + IspcRenderer::IspcRenderer(Scene &scene) : HostRenderer{scene} + { + ispcScene = ispc::Scene_create(); + } + + IspcRenderer::~IspcRenderer() + { + ispc::Scene_destroy(ispcScene); + ispcScene = nullptr; + } + + void IspcRenderer::beforeStart() + { + HostRenderer::beforeStart(); + + ispc::Scene_setVolume(ispcScene, scene.volume.getVolume()); + ispc::Scene_setSampler(ispcScene, scene.volume.getSampler()); + } + + void IspcRenderer::beforeFrame(bool &needToClear) + { + bool parentNeedsToClear = false; + HostRenderer::beforeFrame(parentNeedsToClear); + + if (parentNeedsToClear) { + ispc::Scene_setRendererParams( + ispcScene, + rendererParams->attributeIndex, + rendererParams->time, + rendererParams->fixedFramebufferSize && + rendererParams->restrictPixelRange, + reinterpret_cast(rendererParams->pixelRange)); + + ispc::Scene_setTransferFunction( + ispcScene, + reinterpret_cast( + rendererParams->transferFunction.valueRange), + rendererParams->transferFunction.colorsAndOpacities.size(), + reinterpret_cast( + rendererParams->transferFunction.colorsAndOpacities.data())); + + const AffineSpace3f ctw = cam->getCameraToWorld(); + const vec3f ctw_R0 = ctw.l.row0(); + const vec3f ctw_R1 = ctw.l.row1(); + const vec3f ctw_R2 = ctw.l.row2(); + + ispc::Scene_setCamera(ispcScene, + cam->getSensorWidth(), + cam->getFocalLength(), + reinterpret_cast(ctw_R0), + reinterpret_cast(ctw_R1), + reinterpret_cast(ctw_R2), + reinterpret_cast(ctw.p)); + + needToClear = true; + } + } + + void IspcRenderer::renderFrameImpl(bool clearFramebuffer) + { + const auto startFrame = Framebuffer::Stats::Clock::now(); + + if (scheduler.workerMustTerminate(*this)) { + return; + } + + auto &bBuf = framebuffer.getBackBuffer(); + vec4f *rgba = bBuf.getRgba(); + float *weight = bBuf.getWeight(); + + auto &fBuf = framebuffer.getFrontBuffer(); + + scheduler.locked(bBuf, [&]() { + const size_t ww = bBuf.getWidth(); + const size_t hh = bBuf.getHeight(); + + if (ww == 0 || hh == 0) { + return; + } + + const size_t numPixels = ww * hh; + const size_t pixelsPerJob = ispc::Renderer_pixelsPerJob(); + const size_t numJobs = numPixels / pixelsPerJob; + + const auto startRender = Framebuffer::Stats::Clock::now(); + rkcommon::tasking::parallel_for(numJobs, [&](size_t i) { + if (scheduler.workerMustTerminate(*this)) { + return; + } + + const size_t ib = i * pixelsPerJob; + + if (clearFramebuffer) { + std::memset(rgba + ib, 0, sizeof(vec4f) * pixelsPerJob); + std::memset(weight + ib, 0, sizeof(float) * pixelsPerJob); + } + + renderPixelBlock( + vec2i(ww, hh), static_cast(ib), rgba, weight); + }); + + const auto endRender = Framebuffer::Stats::Clock::now(); + + // Copy output into the front buffer and prepare for display! + scheduler.locked(fBuf, [&]() { + fBuf.copy(bBuf); + fBuf.tonemapInPlace(); + fBuf.getStats().renderTime = endRender - startRender; + const auto endFrame = Framebuffer::Stats::Clock::now(); + fBuf.getStats().frameTime = endFrame - startFrame; + }); + }); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Renderer.h b/examples/interactive/renderer/Renderer.h new file mode 100644 index 00000000..8a6572d2 --- /dev/null +++ b/examples/interactive/renderer/Renderer.h @@ -0,0 +1,169 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Framebuffer.h" +#include "Ray.h" +#include "Scene.h" +#include "Scheduler.h" + +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + using GLuint = unsigned int; + + /* + * All renderers must implement this interface. + */ + class Renderer + { + public: + Renderer(Scene &scene); + virtual ~Renderer(); + + const Scene &getScene() const + { + return scene; + } + + Scene &getScene() + { + return scene; + } + + /* + * Retrieve the current framebuffer, either in raw form or as an + * Open GL texture handle. + * Note that these may be implemented in terms of each other, depending + * on whether the rendering happens on the GPU or CPU. + * + * The inputs w and h are just hints; the renderer is free to ignore them. + * The caller should check the output for its resolution. + */ + virtual const Framebuffer &getFramebuffer(size_t w, size_t h) = 0; + + /* + * This method is called by the Scheduler just before the renderer + * is started. + * It can be used to set up resources that depend on the volume. + */ + virtual void beforeStart() {} + virtual void afterStop() {} + + /* + * This method must be called by renderers just before the frame is + * rendered. It has two jobs: to update parameter structs from the + * GUI, and to determine if the framebuffer needs to be cleared. + * + * Derived classes should call this method on their parent class! + */ + virtual void beforeFrame(bool &needToClear); + + /* + * And a callback for when the frame has finished. + * Again, renderers should call the base class method if they override. + */ + virtual void afterFrame(){}; + + protected: + Scene &scene; + const Scheduler &scheduler; + + // Derived classes may use the entities below in + // renderFrame and methods called by renderFrame. + // They are kept up to date by beforeFrame(...). + Versioned cam; + Versioned rendererParams; + + protected: + void renderFrame() + { + bool clearFramebuffer = false; + beforeFrame(clearFramebuffer); + renderFrameImpl(clearFramebuffer); + afterFrame(); + } + + /* + * This method is called only from the scheduler and may or may not + * run in a separate thread. + * + * Its job is to update the framebuffer. + */ + virtual void renderFrameImpl(bool clearFramebuffer) = 0; + + vec4f sampleTransferFunction(float value) const; + + private: + friend class Scheduler; + friend class Scheduler::Synchronous; + friend class Scheduler::Asynchronous; + + // Used from the asynchronous scheduler + std::thread renderThread; + std::atomic_bool run{false}; + }; + + /* + * A renderer based on the host. + * This base class expects the renderer to fill the front buffer + * in renderFrameImpl(). + */ + class HostRenderer : public Renderer + { + public: + HostRenderer(Scene &scene); + ~HostRenderer(); + + const Framebuffer &getFramebuffer(size_t w, size_t h) override final; + + protected: + Framebuffer framebuffer; + }; + + /* + * A renderer based on the host, which does not use any vectorization. + */ + class ScalarRenderer : public HostRenderer + { + public: + ScalarRenderer(Scene &scene); + + protected: + void renderFrameImpl(bool clearFramebuffer) override final; + virtual void renderPixel(size_t seed, + Ray &ray, + vec4f &rgba, + float &weight) const = 0; + }; + + /* + * A renderer based on the host, but using vectorization with ISPC. + */ + class IspcRenderer : public HostRenderer + { + public: + IspcRenderer(Scene &scene); + ~IspcRenderer(); + void beforeStart() override; + void beforeFrame(bool &needToClear) override; + + protected: + void renderFrameImpl(bool clearFramebuffer) override final; + virtual void renderPixelBlock(const vec2i &resolution, + uint32_t block, + vec4f *rgbas, + float *weights) const = 0; + + protected: + void *ispcScene{nullptr}; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Renderer.ispc b/examples/interactive/renderer/Renderer.ispc new file mode 100644 index 00000000..b53032cd --- /dev/null +++ b/examples/interactive/renderer/Renderer.ispc @@ -0,0 +1,7 @@ +// Copyright 2019-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +export uniform int Renderer_pixelsPerJob() +{ + return programCount; +} diff --git a/examples/interactive/renderer/RendererParams.cpp b/examples/interactive/renderer/RendererParams.cpp new file mode 100644 index 00000000..491a78ec --- /dev/null +++ b/examples/interactive/renderer/RendererParams.cpp @@ -0,0 +1,65 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "RendererParams.h" +#include "CommandLine.h" +#include "Scene.h" + +#include "openvkl_testing.h" + +#include +#include + +using namespace openvkl::testing; + +namespace openvkl { + namespace examples { + + void RendererParams::usage() const + { + std::cerr << "\t-valueRange \n" + "\t-framebufferSize \n" + "\t-pixelRange \n"; + std::cerr << std::flush; + } + + void RendererParams::parseCommandLine(std::list &args) + { + range1f initialValueRange{0.f, 1.f}; + + for (auto it = args.begin(); it != args.end();) { + const std::string arg = *it; + if (arg == "-framebufferSize") { + std::tie(framebufferSize.x, framebufferSize.y) = + cmd::consume_2(args, it); + fixedFramebufferSize = true; + } else if (arg == "-pixelRange") { + std::tie(pixelRange.lower.x, + pixelRange.lower.y, + pixelRange.upper.x, + pixelRange.upper.y) = + cmd::consume_4(args, it); + restrictPixelRange = true; + if (!fixedFramebufferSize) { + fixedFramebufferSize = true; + framebufferSize.x = pixelRange.upper.x; + framebufferSize.y = pixelRange.upper.y; + } + } else if (arg == "-valueRange") { + std::tie(initialValueRange.lower, initialValueRange.upper) = + cmd::consume_2(args, it); + } else if (arg == "-time") { + time = cmd::consume_1(args, it); + } else if (arg == "-attribute") { + attributeIndex = cmd::consume_1(args, it); + } else { + ++it; + } + } + + transferFunction.valueRange = initialValueRange; + } + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/renderer/RendererParams.h b/examples/interactive/renderer/RendererParams.h new file mode 100644 index 00000000..856ca7a8 --- /dev/null +++ b/examples/interactive/renderer/RendererParams.h @@ -0,0 +1,38 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + + #include "TransferFunction.h" + #include + #include + #include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + struct Scene; + + struct RendererParams + { + int attributeIndex{0}; + float time {0.f}; + + // When this is on, ask all renderers to use this framebuffer size. + bool fixedFramebufferSize {false}; + vec2i framebufferSize {1024, 768}; + + // Only render pixels in this range. + bool restrictPixelRange {false}; + region2i pixelRange {vec2i(0, 0), vec2i(1024, 768)}; + + TransferFunction transferFunction; + + void parseCommandLine(std::list &args); + void usage() const; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/RendererParams.ih b/examples/interactive/renderer/RendererParams.ih new file mode 100644 index 00000000..ac028827 --- /dev/null +++ b/examples/interactive/renderer/RendererParams.ih @@ -0,0 +1,17 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +struct RendererParams +{ + int attributeIndex; + float time; + + // Only render pixels in this range. + bool restrictPixelRange; + box2i pixelRange; +}; diff --git a/examples/interactive/renderer/SamplerParams.cpp b/examples/interactive/renderer/SamplerParams.cpp new file mode 100644 index 00000000..f6e7c312 --- /dev/null +++ b/examples/interactive/renderer/SamplerParams.cpp @@ -0,0 +1,57 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "SamplerParams.h" +#include "CommandLine.h" +#include "VolumeParams.h" + +namespace openvkl { + namespace examples { + + void SamplerParams::parseCommandLine(std::list &args, + const VolumeParams &volumeParams) + { + for (auto it = args.begin(); it != args.end();) { + const std::string arg = *it; + + if (arg == "-filter") { + const std::string filterArg = cmd::consume_1(args, it); + filter = stringToFilter(filterArg); + gradientFilter = filter; + } else { + ++it; + } + } + + validate(volumeParams); + } + + void SamplerParams::validate(const VolumeParams &volumeParams) const + { + const std::vector &supFilters = + supportedFilters(volumeParams.volumeType); + if (!supFilters.empty()) { + if (std::find(supFilters.begin(), supFilters.end(), filter) == + supFilters.end()) { + throw std::runtime_error("invalid filter for volume type " + + volumeParams.volumeType); + } + } + } + + void SamplerParams::usage() + { + std::cerr << "\t-filter FILTER (vdb, structuredRegular and " + "structuredSpherical)\n"; + } + + void SamplerParams::updateSampler(VKLSampler sampler) const + { + vklSetInt(sampler, "filter", filter); + vklSetInt(sampler, "gradientFilter", gradientFilter); + vklSetInt(sampler, "maxSamplingDepth", maxSamplingDepth); + vklCommit(sampler); + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/SamplerParams.h b/examples/interactive/renderer/SamplerParams.h new file mode 100644 index 00000000..383156a4 --- /dev/null +++ b/examples/interactive/renderer/SamplerParams.h @@ -0,0 +1,82 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + struct VolumeParams; + + inline std::string filterToString(VKLFilter filter) + { + switch (filter) { + case VKL_FILTER_NEAREST: + return "nearest"; + case VKL_FILTER_TRILINEAR: + return "trilinear"; + case VKL_FILTER_TRICUBIC: + return "tricubic"; + default: + break; + } + return ""; + } + + inline VKLFilter stringToFilter(const std::string &filterArg) + { + if (filterArg == "trilinear") + return VKL_FILTER_TRILINEAR; + else if (filterArg == "tricubic") + return VKL_FILTER_TRICUBIC; + else if (filterArg == "nearest") + return VKL_FILTER_NEAREST; + else + throw std::runtime_error("unsupported filter specified: " + filterArg); + return VKL_FILTER_TRILINEAR; + } + + + struct SamplerParams + { + int maxSamplingDepth = VKL_VDB_NUM_LEVELS - 1; + VKLFilter filter{VKL_FILTER_TRILINEAR}; + VKLFilter gradientFilter{VKL_FILTER_TRILINEAR}; + + void parseCommandLine(std::list &args, + const VolumeParams &volumeParams); + static void usage(); + void updateSampler(VKLSampler sampler) const; + + static const std::vector &supportedFilters( + const std::string &volumeType) + { + if (volumeType == "structuredRegular" + || volumeType == "structuredSpherical" + || volumeType == "vdb") + { + static const std::vector types = { + VKL_FILTER_NEAREST, + VKL_FILTER_TRILINEAR, + VKL_FILTER_TRICUBIC + }; + return types; + } + + static const std::vector types; + return types; + } + + private: + void validate(const VolumeParams &volumeParams) const; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Scene.cpp b/examples/interactive/renderer/Scene.cpp new file mode 100644 index 00000000..df57a6da --- /dev/null +++ b/examples/interactive/renderer/Scene.cpp @@ -0,0 +1,249 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Scene.h" +#include "CommandLine.h" +#include "DensityPathTracer.h" +#include "HitIteratorRenderer.h" +#include "IntervalIteratorDebug.h" +#include "RayMarchIteratorRenderer.h" +#include "Renderer.h" + +#include +#include + +namespace openvkl { + namespace examples { + + Volume::Volume() = default; + + Volume::Volume(Volume &&other) + { + using std::swap; + swap(volumeParams, other.volumeParams); + swap(samplerParams, other.samplerParams); + swap(testingVolume, other.testingVolume); + swap(vklSampler, other.vklSampler); + } + + Volume &Volume::operator=(Volume &&other) + { + if (this != &other) { + using std::swap; + swap(volumeParams, other.volumeParams); + swap(samplerParams, other.samplerParams); + swap(testingVolume, other.testingVolume); + swap(vklSampler, other.vklSampler); + } + return *this; + } + + Volume::~Volume() + { + if (vklSampler) { + vklRelease(vklSampler); + vklSampler = nullptr; + } + vklVolume = nullptr; + } + + void Volume::parseCommandLine(std::list &args) + { + volumeParams.parseCommandLine(args); + samplerParams.parseCommandLine(args, volumeParams); + } + + void Volume::usage() const + { + volumeParams.usage(); + samplerParams.usage(); + } + + void Volume::updateVKLObjects() + { + // VolumeParams::createVolume may throw with invalid params. We try + // to be robust to that, and avoid updating the volume in this case. + try { + if (!vklVolume || volumeNeedsUpdate) { + auto newVolume = volumeParams.createVolume(); + + if (vklSampler) { + vklRelease(vklSampler); + vklSampler = nullptr; + } + + testingVolume = std::move(newVolume); + vklVolume = testingVolume->getVKLVolume(getOpenVKLDevice()); + reinterpret_cast(volumeBounds) = + vklGetBoundingBox(vklVolume); + numAttributes = vklGetNumAttributes(vklVolume); + } + + if (!vklSampler) { + vklSampler = vklNewSampler(vklVolume); + samplerNeedsUpdate = true; + } + + if (samplerNeedsUpdate) { + samplerParams.updateSampler(vklSampler); + } + } catch (const std::exception &e) { + if (testingVolume) { + std::cerr << e.what() << std::endl; + } else { + // If we have no volume at all, we terminate. This will be caught + // in vklExamples::main(). + throw; + } + } + + // If the update failed, do not try to update again. + volumeNeedsUpdate = false; + samplerNeedsUpdate = false; + } + + // ------------------------------------------------------------------------- + const std::vector &Scene::supportedRendererTypes() const + { + static const std::vector types = { + "density_pathtracer", + "density_pathtracer_ispc", + "hit_iterator", + "hit_iterator_ispc", + "ray_march_iterator", + "ray_march_iterator_ispc", + "interval_iterator_debug", + "interval_iterator_debug_ispc"}; + return types; + } + + bool Scene::validateRendererType(const std::string &type) const + { + const auto &types = supportedRendererTypes(); + return (std::find(types.begin(), types.end(), type) != types.end()); + } + + std::unique_ptr Scene::createRenderer(const std::string &type) + { + std::unique_ptr ptr; + + if (type == "density_pathtracer") { + ptr = rkcommon::make_unique(*this); + } else if (type == "density_pathtracer_ispc") { + ptr = rkcommon::make_unique(*this); + } else if (type == "hit_iterator") { + ptr = rkcommon::make_unique(*this); + } else if (type == "hit_iterator_ispc") { + ptr = rkcommon::make_unique(*this); + } else if (type == "interval_iterator_debug") { + ptr = rkcommon::make_unique(*this); + } else if (type == "interval_iterator_debug_ispc") { + ptr = rkcommon::make_unique(*this); + } else if (type == "ray_march_iterator") { + ptr = rkcommon::make_unique(*this); + } else if (type == "ray_march_iterator_ispc") { + ptr = rkcommon::make_unique(*this); + } + + return ptr; + } + + bool Scene::parseCommandLine(const std::list &_args) + { + std::list args = _args; + + bool terminate = false; + bool printUsage = false; + bool synchronous = false; + + if (args.empty()) { + std::runtime_error( + "invalid argument list provided to Scene::parseCommandLine"); + } + const std::string programName = args.front(); + args.pop_front(); + + try { + for (auto it = args.begin(); it != args.end() && !terminate;) { + const std::string arg = *it; + if (arg == "-h" || arg == "-help" || arg == "--help") { + cmd::consume_0(args, it); + terminate = true; + printUsage = true; + } else if (arg == "-batch") { + cmd::consume_0(args, it); + interactive = false; + } else if (arg == "-spp") { + batchModeSpp = cmd::consume_1(args, it); + } else if (arg == "-disable-vsync") { + cmd::consume_0(args, it); + disableVSync = true; + } else if (arg == "-renderer") { + const std::string type = cmd::consume_1(args, it); + if (type == "all") { + rendererTypes = supportedRendererTypes(); + } else if (validateRendererType(type)) { + rendererTypes.push_back(type); + } else { + std::cerr << "unknown renderer type: " << type << std::endl; + terminate = true; + } + } else if (arg == "-sync") { + cmd::consume_0(args, it); + synchronous = true; + } else { + ++it; + } + } + + if (!terminate) { + volume.parseCommandLine(args); + rendererParams->parseCommandLine(args); + } + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + terminate = true; + } + + for (const auto &arg : args) { + std::cerr << "unknown argument: " << arg << std::endl; + terminate = true; + } + + if (printUsage) { + std::cerr << "usage: " << programName + << "\n" + "\t-h, -help\n" + "\t-batch\n" + "\t-spp SPP (only in -batch mode)\n" + "\t-sync\n" + "\t-disable-vsync\n" + "\t-renderer "; + + const auto &types = supportedRendererTypes(); + for (auto it = types.begin(); it != types.end(); ++it) { + if (it != types.begin()) { + std::cerr << "| "; + } + std::cerr << *it; + } + std::cerr << "\n"; + + volume.usage(); + rendererParams->usage(); + + std::cerr << std::flush; + terminate = true; + } + + if (!interactive) { + synchronous = true; + } + + scheduler = Scheduler(synchronous); + + return !terminate; + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Scene.h b/examples/interactive/renderer/Scene.h new file mode 100644 index 00000000..e4a68749 --- /dev/null +++ b/examples/interactive/renderer/Scene.h @@ -0,0 +1,144 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ArcballCamera.h" +#include "RendererParams.h" +#include "SamplerParams.h" +#include "Versioned.h" +#include "VolumeParams.h" + +#include "rkcommon/math/box.h" +#include "rkcommon/math/vec.h" + +#include +#include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + + class Renderer; + + struct Volume + { + public: + Volume(); + Volume(const Volume &) = delete; + Volume &operator=(const Volume &) = delete; + Volume(Volume &&other); + Volume &operator=(Volume &&other); + ~Volume(); + + public: + // Renderers may only access the volume and sampler. Parameters etc. + // are fully hidden. + VKLVolume getVolume() const + { + return vklVolume; + } + + const box3f &getBounds() const + { + return volumeBounds; + } + + unsigned int getNumAttributes() const + { + return numAttributes; + } + + VKLSampler getSampler() const + { + return vklSampler; + } + + VolumeParams &getVolumeParams() + { + return volumeParams; + } + + void setVolumeDirty() { + volumeNeedsUpdate = true; + } + + bool volumeIsDirty() const { + return volumeNeedsUpdate; + } + + SamplerParams &getSamplerParams() + { + return samplerParams; + } + + void setSamplerDirty() { + samplerNeedsUpdate = true; + } + + void printInfo() const + { + std::cout << "volumeType: " << volumeParams.volumeType << std::endl; + std::cout << "voxelType: " + << voxelTypeToString(volumeParams.voxelType) << std::endl; + std::cout << "gridDimensions: " << volumeParams.dimensions << std::endl; + std::cout << "gridOrigin: " << volumeParams.gridOrigin << std::endl; + std::cout << "gridSpacing: " << volumeParams.gridSpacing + << std::endl; + std::cout << "source: " << volumeParams.source << std::endl; + + std::cout << "boundingBox: " + << "(" << volumeBounds.lower.x << ", " << volumeBounds.lower.y + << ", " << volumeBounds.lower.z << ") -> (" + << volumeBounds.upper.x << ", " << volumeBounds.upper.y + << ", " << volumeBounds.upper.z << ")" << std::endl; + } + + public: + // Command line parsing. This should only be called once on startup. + void parseCommandLine(std::list &args); + void usage() const; + + // Update the VKL volume and sampler objects. + // This should only be called from the main thread, and only if all + // workers have been stopped. + void updateVKLObjects(); + + private: + VolumeParams volumeParams; + SamplerParams samplerParams; + bool volumeNeedsUpdate{true}; + bool samplerNeedsUpdate{true}; + + std::unique_ptr testingVolume; + VKLVolume vklVolume{nullptr}; + box3f volumeBounds; + unsigned int numAttributes{0}; + VKLSampler vklSampler{nullptr}; + }; + + struct Scene + { + Scheduler scheduler; + Volume volume; + Versioned rendererParams; + Versioned camera; + + // General application parameters. These cannot be changed + // through the GUI. + bool disableVSync{false}; + bool interactive{true}; + unsigned batchModeSpp{1}; + std::vector rendererTypes; + + // Returns false if the application should terminate. + bool parseCommandLine(const std::list &args); + + const std::vector &supportedRendererTypes() const; + bool validateRendererType(const std::string &type) const; + std::unique_ptr createRenderer(const std::string &type); + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/Scene.ih b/examples/interactive/renderer/Scene.ih new file mode 100644 index 00000000..396a17a7 --- /dev/null +++ b/examples/interactive/renderer/Scene.ih @@ -0,0 +1,49 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "Camera.ih" +#include "RendererParams.ih" +#include "TransferFunction.ih" + +struct Scene +{ + VKLVolume volume; + VKLSampler sampler; + Camera camera; + RendererParams rendererParams; + TransferFunction transferFunction; +}; + +// Returns false for pixels outside the valid range. +// pixelIdx is the linear pixel index for each lane. +inline bool Scene_computePixel(const uniform Scene &scene, + int w, + int h, + uint32 pixelIdx, + vec2i &pixel) +{ + bool inRange = false; + + if (w > 0) { + const int x = pixelIdx % w; + const int y = pixelIdx / w; + inRange = (x < w && y < h); + + if (scene.rendererParams.restrictPixelRange) { + const uniform box2i &range = scene.rendererParams.pixelRange; + inRange = inRange && (h - y - 1) >= range.lower.y && + (h - y - 1) < range.upper.y && (w - x - 1) >= range.lower.x && + (w - x - 1) < range.upper.x; + } + + if (inRange) { + pixel.x = x; + pixel.y = y; + } + } + + return inRange; +} diff --git a/examples/interactive/renderer/Scene.ispc b/examples/interactive/renderer/Scene.ispc new file mode 100644 index 00000000..969cb784 --- /dev/null +++ b/examples/interactive/renderer/Scene.ispc @@ -0,0 +1,71 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Scene.ih" + +export void *uniform Scene_create() +{ + Scene *uniform scene = uniform new Scene; + memset(scene, 0, sizeof(uniform Scene)); + return scene; +} + +export void Scene_destroy(void *uniform self) +{ + delete ((Scene * uniform) self); +} + +export void Scene_setVolume(void *uniform self, void *uniform volume) +{ + ((Scene * uniform) self)->volume = ((uniform VKLVolume)volume); +} + +export void Scene_setSampler(void *uniform self, void *uniform sampler) +{ + ((Scene * uniform) self)->sampler = ((uniform VKLSampler)sampler); +} + +export void Scene_setCamera(void *uniform self, + uniform float sensorWidth, + uniform float focalLength, + const uniform vec3f &cameraToWorld_R0, + const uniform vec3f &cameraToWorld_R1, + const uniform vec3f &cameraToWorld_R2, + const uniform vec3f &cameraToWorld_t) +{ + uniform Camera &camera = ((Scene * uniform) self)->camera; + + camera.sensorWidth = sensorWidth; + camera.focalLength = focalLength; + camera.ctw_R[0] = cameraToWorld_R0; + camera.ctw_R[1] = cameraToWorld_R1; + camera.ctw_R[2] = cameraToWorld_R2; + camera.ctw_t = cameraToWorld_t; +} + + +export void Scene_setRendererParams(void *uniform self, + uniform int attributeIndex, + uniform float time, + uniform bool restrictPixelRange, + uniform const box2i &pixelRange) +{ + uniform RendererParams ¶ms = ((Scene * uniform) self)->rendererParams; + + params.attributeIndex = attributeIndex; + params.time = time; + params.restrictPixelRange = restrictPixelRange; + params.pixelRange = pixelRange; +} + +export void Scene_setTransferFunction(void *uniform self, + const uniform box1f &valueRange, + uniform size_t numValues, + const vec4f *uniform colorsAndOpacities) +{ + uniform TransferFunction &tf = ((Scene * uniform) self)->transferFunction; + + tf.valueRange = valueRange; + tf.numValues = numValues; + tf.colorsAndOpacities = colorsAndOpacities; +} diff --git a/examples/interactive/renderer/Scheduler.cpp b/examples/interactive/renderer/Scheduler.cpp new file mode 100644 index 00000000..f78ad21b --- /dev/null +++ b/examples/interactive/renderer/Scheduler.cpp @@ -0,0 +1,71 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "Scheduler.h" +#include "Renderer.h" + +namespace openvkl { + namespace examples { + + void Scheduler::Synchronous::renderFrame(Renderer &renderer) const + { + renderer.renderFrame(); + } + + // ------------------------------------------------------------------------- + + void Scheduler::Asynchronous::start(Renderer &renderer) const + { + if (renderer.run.load()) { + return; + } + + renderer.run.store(true); + + renderer.renderThread = std::thread([&renderer]() { + while (renderer.run.load()) { + renderer.renderFrame(); + } + }); + } + + void Scheduler::Asynchronous::stop(Renderer &renderer) const + { + if (renderer.run.load()) { + renderer.run.store(false); + renderer.renderThread.join(); + } + } + + void Scheduler::Asynchronous::lock(Lock &lock) const + { + lock.lock(); + } + + void Scheduler::Asynchronous::unlock(Lock &lock) const + { + lock.unlock(); + } + + bool Scheduler::Asynchronous::workerMustTerminate(Renderer &renderer) const + { + return !renderer.run.load(); + } + + // ------------------------------------------------------------------------- + + void Scheduler::start(Renderer &renderer) const + { + renderer.beforeStart(); + impl->start(renderer); + } + + void Scheduler::stop(Renderer &renderer) const + { + impl->stop(renderer); + renderer.afterStop(); + } + + } // namespace examples +} // namespace openvkl + diff --git a/examples/interactive/renderer/Scheduler.h b/examples/interactive/renderer/Scheduler.h new file mode 100644 index 00000000..a60d9851 --- /dev/null +++ b/examples/interactive/renderer/Scheduler.h @@ -0,0 +1,114 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + /* + * All renderers must implement this interface. + */ + class Renderer; + + /* + * This class schedules rendering, either synchronously or + * asynchronously. + */ + class Scheduler + { + public: + using Mutex = std::mutex; + using Lock = std::unique_lock; + + class Lockable + { + public: + virtual ~Lockable() = default; + + private: + friend class Scheduler; + mutable Mutex lockableMutex; + }; + + class Impl + { + public: + virtual ~Impl() = default; + virtual void start(Renderer &renderer) const = 0; + virtual void renderFrame(Renderer &renderer) const = 0; + virtual void stop(Renderer &renderer) const = 0; + virtual void lock(Lock &lock) const = 0; + virtual void unlock(Lock &lock) const = 0; + virtual bool workerMustTerminate(Renderer &renderer) const = 0; + }; + + class Synchronous : public Impl + { + public: + void start(Renderer &renderer) const override final {} + void renderFrame(Renderer &renderer) const override final; + void stop(Renderer &renderer) const override final {} + void lock(Lock & /*lock*/) const override final {} + void unlock(Lock & /*lock*/) const override final {} + bool workerMustTerminate(Renderer &renderer) const override final + { + return false; + } + }; + + class Asynchronous : public Impl + { + public: + void start(Renderer &renderer) const override final; + void renderFrame(Renderer &renderer) const override final {} + void stop(Renderer &renderer) const override final; + void lock(Lock &lock) const override final; + void unlock(Lock &lock) const override final; + bool workerMustTerminate(Renderer &renderer) const override final; + }; + + public: + explicit Scheduler(bool synchronous = true) + { + if (synchronous) { + impl = rkcommon::make_unique(); + } else { + impl = rkcommon::make_unique(); + } + } + + void start(Renderer &renderer) const; + void stop(Renderer &renderer) const; + + void renderFrame(Renderer &renderer) const + { + impl->renderFrame(renderer); + } + + bool workerMustTerminate(Renderer &renderer) const + { + return impl->workerMustTerminate(renderer); + } + + template + void locked(const Lockable &lockable, const F &functor) const + { + std::unique_lock lock(lockable.lockableMutex, + std::defer_lock); + impl->lock(lock); + functor(); + impl->unlock(lock); + } + + private: + std::unique_ptr impl; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderers/TransferFunction.h b/examples/interactive/renderer/TransferFunction.h similarity index 98% rename from examples/interactive/renderers/TransferFunction.h rename to examples/interactive/renderer/TransferFunction.h index 5283f81e..3390909e 100644 --- a/examples/interactive/renderers/TransferFunction.h +++ b/examples/interactive/renderer/TransferFunction.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Intel Corporation +// Copyright 2019-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #pragma once @@ -117,3 +117,4 @@ namespace openvkl { } // namespace examples } // namespace openvkl + diff --git a/examples/interactive/renderer/TransferFunction.ih b/examples/interactive/renderer/TransferFunction.ih new file mode 100644 index 00000000..fa362cc1 --- /dev/null +++ b/examples/interactive/renderer/TransferFunction.ih @@ -0,0 +1,48 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +struct TransferFunction +{ + box1f valueRange; + size_t numValues; + const vec4f *uniform colorsAndOpacities; +}; + +inline vec4f TransferFunction_sample(const uniform TransferFunction &self, + float value) +{ + vec4f colorAndOpacity = make_vec4f(0.f); + + if (isnan(value) || self.numValues == 0) { + return colorAndOpacity; + } + + if (value <= self.valueRange.lower) { + return self.colorsAndOpacities[0]; + } + + if (value >= self.valueRange.upper) { + return self.colorsAndOpacities[self.numValues - 1]; + } + + // map the value into the range [0, size - 1] + value = (value - self.valueRange.lower) / + (self.valueRange.upper - self.valueRange.lower) * + (self.numValues - 1.f); + + // index and fractional offset + const int index = floor(value); + const float remainder = value - index; + + // the final interpolated value + return ( + (1.f - remainder) * self.colorsAndOpacities[index] + + remainder * + self.colorsAndOpacities[min( + ((int32)(index + 1)), ((uniform int32)(self.numValues - 1)))]); +} diff --git a/examples/interactive/renderer/Versioned.h b/examples/interactive/renderer/Versioned.h new file mode 100644 index 00000000..e0399b29 --- /dev/null +++ b/examples/interactive/renderer/Versioned.h @@ -0,0 +1,72 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include "Scheduler.h" + +namespace openvkl { + namespace examples { + + template + class Versioned : public Scheduler::Lockable + { + public: + uint64_t getVersion() const + { + return version.load(); + } + + void incrementVersion() + { + ++version; + } + + /* + * Copy our internal object if the version has changed. + * Returns true if the output was updated. + */ + bool updateIfChanged(const Versioned &other) + { + bool changed = (version != other.version); + if (changed) { + obj = other.obj; + version.store(other.version.load()); + } + return changed; + } + + template + Versioned(Args &&...args) : obj{std::forward(args)...} + { + } + + T &operator*() + { + return obj; + } + + const T &operator*() const + { + return obj; + } + + T *operator->() + { + return &obj; + } + + const T *operator->() const + { + return &obj; + } + + private: + std::atomic version{static_cast(-1)}; + T obj; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/VolumeParams.cpp b/examples/interactive/renderer/VolumeParams.cpp new file mode 100644 index 00000000..96a320bb --- /dev/null +++ b/examples/interactive/renderer/VolumeParams.cpp @@ -0,0 +1,635 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "VolumeParams.h" +#include "CommandLine.h" + +#include // make_unique + +#include +#include +#include +#include + +// NOTE: Cannot use unordered_map with VKLDataType since libstdc++ has a bug +// on Centos 7/ICC15, and this prevents us from implementing a hash functor. +#include + +using namespace openvkl::testing; + +namespace openvkl { + namespace examples { + + // Note: Will only work for three letter extensions. + inline std::string getFileExtension(const std::string &filename) + { + std::string ext; + if (!filename.empty()) { + ext = filename.substr(filename.size() - 4); + std::for_each( + ext.begin(), ext.end(), [](char &c) { c = ::tolower(c); }); + } + return ext; + } + + void VolumeParams::usage() + { + std::cerr + << "\t-volumeType structuredRegular | structuredSpherical | " + "unstructured | amr | vdb | particle\n" + "\t-gridOrigin \n" + "\t-gridSpacing \n" + "\t-gridDimensions \n" + "\t-voxelType uchar | short | ushort | half | float | double\n" + "\t-multiAttribute (vdb and structuredRegular only, ignores " + "-field)\n" + "\t-motionBlur structured | unstructured (structuredRegular and " + "vdb)\n" + "\t-field wavelet | xyz | sphere | torus (vdb float only) | " + " (-file only)\n" + "\t-file \n" + "\t-numParticles (particle only)\n" + "\t-background | undefined\n"; + } + + void VolumeParams::parseCommandLine(std::list &args) + { + std::string fieldParam; + for (auto it = args.begin(); it != args.end();) { + const std::string arg = *it; + + if (arg == "-volumeType") { + volumeType = cmd::consume_1(args, it); + } + + else if (arg == "-file") { + filename = cmd::consume_1(args, it); + source = "file"; + } + + else if (arg == "-field") { + fieldParam = cmd::consume_1(args, it); + } + + else if (arg == "-voxelType") { + const auto vt = cmd::consume_1(args, it); + voxelType = stringToVoxelType(vt); + } + + else if (arg == "-background") { + try { + background = cmd::consume_1(args, it); + } catch (std::runtime_error) { + const std::string bgStr = cmd::consume_1(args, it); + if (bgStr == "undefined") + background = VKL_BACKGROUND_UNDEFINED; + else + throw std::runtime_error("-background: invalid argument"); + } + } + + else if (arg == "-numParticles") { + numParticles = cmd::consume_1(args, it); + } + + else if (arg == "-gridOrigin") { + std::tie(gridOrigin.x, gridOrigin.y, gridOrigin.z) = + cmd::consume_3(args, it); + } + + else if (arg == "-gridSpacing") { + std::tie(gridSpacing.x, gridSpacing.y, gridSpacing.z) = + cmd::consume_3(args, it); + } + + else if (arg == "-gridDimensions") { + std::tie(dimensions.x, dimensions.y, dimensions.z) = + cmd::consume_3(args, it); + } + + else if (arg == "-multiAttribute") { + cmd::consume_0(args, it); + multiAttribute = true; + } + + else if (arg == "-motionBlur") { + const auto mbType = cmd::consume_1(args, it); + if (mbType == "structured") + motionBlurStructured = true; + else if (mbType == "unstructured") + motionBlurUnstructured = true; + else + throw std::runtime_error("unknown motion blur type " + mbType); + } + + else { + ++it; + } + } + + if (!fieldParam.empty()) { + if (filename.empty()) { + field = fieldParam; + source = "procedural"; + } else { + fieldInFile = fieldParam; + source = "file"; + } + } + + generateGridTransform(); + validate(); + } + + void VolumeParams::generateGridTransform() + { + // generate gridOrigin and gridSpacing if not specified on + // the command-line + if (std::isnan(gridOrigin.x) || std::isnan(gridSpacing.x)) { + const float boundingBoxSize = 2.f; + + if (volumeType == "structuredSpherical") { + ProceduralStructuredSphericalVolume<>::generateGridParameters( + dimensions, boundingBoxSize, gridOrigin, gridSpacing); + } else { + // all other grid types can use values generated for structured + // regular volumes + ProceduralStructuredRegularVolume<>::generateGridParameters( + dimensions, boundingBoxSize, gridOrigin, gridSpacing); + } + } + } + + void VolumeParams::validate() const + { + const std::vector &supVt = supportedVolumeTypes(); + if (std::find(supVt.begin(), supVt.end(), volumeType) == supVt.end()) { + throw std::runtime_error("invalid volume type " + volumeType); + } + + const std::vector &supSources = supportedSources(volumeType); + if (std::find(supSources.begin(), supSources.end(), source) == + supSources.end()) { + throw std::runtime_error("invalid source " + source); + } + + if (filename.empty()) { + const std::vector &supFields = supportedFields(volumeType); + if (std::find(supFields.begin(), supFields.end(), field) == + supFields.end()) { + throw std::runtime_error("invalid field " + field); + } + } + + const std::vector &supVoxelTypes = + supportedVoxelTypes(volumeType); + if (!supVoxelTypes.empty() && + std::find(supVoxelTypes.begin(), supVoxelTypes.end(), voxelType) == + supVoxelTypes.end()) { + throw std::runtime_error("invalid voxelType " + + voxelTypeToString(voxelType)); + } + } + + std::unique_ptr VolumeParams::createVolume() const + { + // We make a copy so that we can fix things automagically without + // having to make this function non-const. + VolumeParams pp = *this; + pp.validate(); + + std::unique_ptr testingVolume; + + if (volumeType == "structuredRegular") { + testingVolume = pp.createStructuredRegularVolume(); + } else if (volumeType == "structuredSpherical") { + testingVolume = pp.createStructuredSphericalVolume(); + } else if (volumeType == "unstructured") { + testingVolume = pp.createUnstructuredVolume(); + } else if (volumeType == "amr") { + testingVolume = pp.createAmrVolume(); + } else if (volumeType == "vdb") { + testingVolume = pp.createVdbVolume(); + } else if (volumeType == "particle") { + testingVolume = pp.createParticleVolume(); + } + + // The above calls may throw, in which case we never execute anything + // below. + assert(testingVolume); + + if (testingVolume) { + VKLVolume vklVolume = testingVolume->getVKLVolume(getOpenVKLDevice()); + vklSetFloat(vklVolume, "background", pp.background); + vklCommit(vklVolume); + } + + return testingVolume; + } + + std::unique_ptr + VolumeParams::createStructuredRegularVolume() const + { + if (source == "file") { + const std::string ext = getFileExtension(filename); + if (ext == ".rwh") { + return rkcommon::make_unique( + filename, volumeType, gridOrigin, gridSpacing); + } else { + return rkcommon::make_unique(filename, + volumeType, + dimensions, + gridOrigin, + gridSpacing, + voxelType); + } + } + + // Source is not a file. + const TemporalConfig temporalConfig = createTemporalConfig(); + + if (multiAttribute) { + return std::unique_ptr( + generateMultiAttributeStructuredRegularVolume( + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + VKL_DATA_SHARED_BUFFER, + false)); + } + + // + + if (voxelType == VKL_UCHAR) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_UCHAR + + if (voxelType == VKL_SHORT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_SHORT + + if (voxelType == VKL_USHORT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_USHORT + + if (voxelType == VKL_HALF) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_HALF + + if (voxelType == VKL_FLOAT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_FLOAT + + if (voxelType == VKL_DOUBLE) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, temporalConfig); + } + } // VKL_DOUBLE + + // Since we validate input, this should never happen except in case + // of a programmer error. + assert(false); + return nullptr; + } + + std::unique_ptr + VolumeParams::createStructuredSphericalVolume() const + { + if (voxelType == VKL_UCHAR) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_UCHAR + + if (voxelType == VKL_SHORT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_SHORT + + if (voxelType == VKL_USHORT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_USHORT + + if (voxelType == VKL_HALF) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_HALF + + if (voxelType == VKL_FLOAT) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_FLOAT + + if (voxelType == VKL_DOUBLE) { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + + else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing); + } + } // VKL_DOUBLE + + // Since we validate input, this should never happen except in case + // of a programmer error. + assert(false); + return nullptr; + } + + std::unique_ptr + VolumeParams::createUnstructuredVolume() const + { + if (field == "xyz") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, VKL_HEXAHEDRON, false); + } else if (field == "sphere") { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, VKL_HEXAHEDRON, false); + } else if (field == "mixed") { + return rkcommon::make_unique(); + } else { + return rkcommon::make_unique( + dimensions, gridOrigin, gridSpacing, VKL_HEXAHEDRON, false); + } + + // Since we validate input, this should never happen except in case + // of a programmer error. + assert(false); + return nullptr; + } + + std::unique_ptr VolumeParams::createAmrVolume() + const + { + return rkcommon::make_unique>( + dimensions, gridOrigin, gridSpacing); + } + + std::unique_ptr VolumeParams::createVdbVolume() + const + { + if (source == "file") { + // avoid deferred loading when exporting innerNodes to ensure exported + // value ranges represent the full data + return std::unique_ptr( + OpenVdbVolume::loadVdbFile(getOpenVKLDevice(), + filename, + fieldInFile, + false /* do not defer leaves. */)); + } + + const TemporalConfig temporalConfig = createTemporalConfig(); + + if (!temporalConfig.hasTime() && multiAttribute) { + if (voxelType == VKL_HALF) { + return std::unique_ptr( + generateMultiAttributeVdbVolumeHalf(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + VKL_DATA_SHARED_BUFFER, + false)); + } else if (voxelType == VKL_FLOAT) { + return std::unique_ptr( + generateMultiAttributeVdbVolumeFloat(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + VKL_DATA_SHARED_BUFFER, + false)); + } + } + + const uint32_t numAttributes = multiAttribute ? 3 : 1; + + if (voxelType == VKL_HALF) { + if (field == "xyz") { + return rkcommon::make_unique(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } else if (field == "sphere") { + return rkcommon::make_unique(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } else { + return rkcommon::make_unique(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } + } // VKL_HALF + + if (voxelType == VKL_FLOAT) { + if (field == "xyz") { + return rkcommon::make_unique(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } else if (field == "sphere") { + return rkcommon::make_unique(getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } else { + return rkcommon::make_unique( + getOpenVKLDevice(), + dimensions, + gridOrigin, + gridSpacing, + temporalConfig, + numAttributes); + } + } // VKL_FLOAT + + // Since we validate input, this should never happen except in case + // of a programmer error. + assert(false); + return nullptr; + } + + std::unique_ptr VolumeParams::createParticleVolume() + const + { + return rkcommon::make_unique(numParticles); + } + + TemporalConfig VolumeParams::createTemporalConfig() const + { + TemporalConfig temporalConfig; + + if (motionBlurStructured) { + temporalConfig = TemporalConfig(TemporalConfig::Structured, + motionBlurStructuredNumTimesteps); + } else if (motionBlurUnstructured) { + if (field == "sphere" && !multiAttribute) { + temporalConfig = TemporalConfig(TemporalConfig::Unstructured, 256); + temporalConfig.useTemporalCompression = true; + temporalConfig.temporalCompressionThreshold = 0.05f; + } else { + std::vector timeSamples = motionBlurUnstructuredTimeSamples; + std::sort(timeSamples.begin(), timeSamples.end()); + temporalConfig = TemporalConfig(timeSamples); + } + } + + return temporalConfig; + } + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderer/VolumeParams.h b/examples/interactive/renderer/VolumeParams.h new file mode 100644 index 00000000..37d7aa1e --- /dev/null +++ b/examples/interactive/renderer/VolumeParams.h @@ -0,0 +1,174 @@ +// Copyright 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "openvkl_testing.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace openvkl { + namespace examples { + + using namespace rkcommon::math; + using namespace openvkl::testing; + + inline VKLDataType stringToVoxelType(const std::string &type) + { + static const std::map map = { + {"uchar", VKL_UCHAR}, + {"short", VKL_SHORT}, + {"ushort", VKL_USHORT}, + {"half", VKL_HALF}, + {"float", VKL_FLOAT}, + {"double", VKL_DOUBLE}}; + + const auto it = map.find(type); + if (it == map.end()) { + return VKL_UNKNOWN; + } + + return it->second; + } + + inline const std::string &voxelTypeToString(VKLDataType type) + { + static const std::map map = { + {VKL_UCHAR, "uchar"}, + {VKL_SHORT, "short"}, + {VKL_USHORT, "ushort"}, + {VKL_HALF, "half"}, + {VKL_FLOAT, "float"}, + {VKL_DOUBLE, "double"}}; + + const auto it = map.find(type); + if (it == map.end()) { + static const std::string uk = "unknown"; + return uk; + } + + return it->second; + } + + struct VolumeParams + { + std::string volumeType{supportedVolumeTypes()[0]}; + std::string source{supportedSources("structuredRegular")[0]}; + std::string filename; + std::string field{supportedFields("structuredRegular")[0]}; + std::string fieldInFile{"density"}; + VKLDataType voxelType{VKL_FLOAT}; + float background{VKL_BACKGROUND_UNDEFINED}; + int numParticles{1000}; + + vec3i dimensions{128}; + vec3f gridOrigin{rkcommon::math::nan}; + vec3f gridSpacing{rkcommon::math::nan}; + + bool multiAttribute{false}; + + bool motionBlurStructured{false}; + bool motionBlurUnstructured{false}; + std::vector motionBlurUnstructuredTimeSamples{ + 0.f, 0.15f, 0.3f, 0.65f, 0.9f, 1.0f}; + uint8_t motionBlurStructuredNumTimesteps{6}; + + void parseCommandLine(std::list &args); + static void usage(); + + // This method will throw if there is an error in creating the volume. + std::unique_ptr createVolume() const; + + private: + void validate() const; + TemporalConfig createTemporalConfig() const; + + std::unique_ptr createStructuredRegularVolume() + const; + std::unique_ptr createStructuredSphericalVolume() + const; + std::unique_ptr createUnstructuredVolume() const; + std::unique_ptr createAmrVolume() const; + std::unique_ptr createVdbVolume() const; + std::unique_ptr createParticleVolume() const; + + public: + void generateGridTransform(); + + // Shared volume params. Only include things here that are valid for + // all volume types, and should remain the same when switching volume + // types. + static const std::vector &supportedVolumeTypes() + { + static std::vector sup = {"structuredRegular", + "structuredSpherical", + "unstructured", + "amr", + "vdb", + "particle"}; + return sup; + }; + + static const std::vector &supportedVoxelTypes( + const std::string &volumeType) + { + if (volumeType == "structuredRegular" || + volumeType == "structuredSpherical") { + static const std::vector types = {VKL_UCHAR, + VKL_SHORT, + VKL_USHORT, + VKL_HALF, + VKL_FLOAT, + VKL_DOUBLE}; + return types; + } + + else if (volumeType == "vdb") { + static const std::vector types = {VKL_HALF, VKL_FLOAT}; + return types; + } + + static const std::vector types; + return types; + } + + static const std::vector &supportedSources( + const std::string &volumeType) + { + if (volumeType == "structuredRegular" || volumeType == "vdb") { + static const std::vector sources = { + "procedural", "file"}; + return sources; + } + static const std::vector sources = { + "procedural"}; + return sources; + }; + + + static const std::vector &supportedFields( + const std::string &volumeType) + { + if (volumeType == "unstructured") { + static const std::vector fields = { + "wavelet", "xyz", "sphere", "mixed"}; + return fields; + } + + static const std::vector fields = { + "wavelet", "xyz", "sphere"}; + return fields; + }; + }; + + } // namespace examples +} // namespace openvkl diff --git a/examples/interactive/renderers/DensityPathTracer.cpp b/examples/interactive/renderers/DensityPathTracer.cpp deleted file mode 100644 index 770b9259..00000000 --- a/examples/interactive/renderers/DensityPathTracer.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "DensityPathTracer.h" -// ispc -#include "DensityPathTracer_ispc.h" - -namespace openvkl { - namespace examples { - - static vec3f cartesian(const float phi, - const float sinTheta, - const float cosTheta) - { - float sinPhi = std::sin(phi); - float cosPhi = std::cos(phi); - return vec3f(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta); - } - - static vec3f uniformSampleSphere(const float radius, const vec2f &s) - { - const float phi = float(two_pi) * s.x; - const float cosTheta = radius * (1.f - 2.f * s.y); - const float sinTheta = 2.f * radius * std::sqrt(s.y * (1.f - s.y)); - return cartesian(phi, sinTheta, cosTheta); - } - - // DensityPathTracer definitions ////////////////////////////////////////// - - DensityPathTracer::DensityPathTracer() - { - ispcEquivalent = ispc::DensityPathTracer_create(); - } - - void DensityPathTracer::commit() - { - Renderer::commit(); - - shutter = getParam("shutter", 0.f); - motionBlur = getParam("motionBlur", false); - sigmaTScale = getParam("sigmaTScale", 1.f); - sigmaSScale = getParam("sigmaSScale", 1.f); - maxNumScatters = getParam("maxNumScatters", 1); - ambientLightIntensity = getParam("ambientLightIntensity", 1.f); - - ispc::DensityPathTracer_set(ispcEquivalent, - shutter, - motionBlur, - sigmaTScale, - sigmaSScale, - maxNumScatters, - ambientLightIntensity); - } - - bool DensityPathTracer::sampleWoodcock(RNG &rng, - const Scene &scene, - const Ray &ray, - const range1f &hits, - float &t, - float &sample, - float &transmittance) - { - t = hits.lower; - - const float sigmaMax = sigmaTScale; - - while (true) { - vec2f randomNumbers = rng.getFloats(); - vec2f randomNumbers2 = rng.getFloats(); - - t = t + -std::log(1.f - randomNumbers.x) / sigmaMax; - - if (t > hits.upper) { - transmittance = 1.f; - return false; - } - - const vec3f c = ray.org + t * ray.dir; - float time = scene.time; - if (motionBlur) { - time = time + (randomNumbers2.x - 0.5f) * this->shutter; - } - time = clamp(time, 0.f, 1.f); - sample = vklComputeSample( - scene.sampler, (const vkl_vec3f *)&c, scene.attributeIndex, time); - - vec4f sampleColorAndOpacity = sampleTransferFunction(scene, sample); - - // sigmaT must be mono-chromatic for Woodcock sampling - const float sigmaTSample = sigmaMax * sampleColorAndOpacity.w; - - if (randomNumbers.y < sigmaTSample / sigmaMax) - break; - } - - transmittance = 0.f; - return true; - } - - void DensityPathTracer::integrate( - RNG &rng, const Scene &scene, Ray &ray, vec3f &Le, int scatterIndex) - { - // initialize emitted light to 0 - Le = vec3f(0.f); - - const auto volumeBounds = vklGetBoundingBox(scene.volume); - ray.t = intersectRayBox( - ray.org, ray.dir, *reinterpret_cast(&volumeBounds)); - if (ray.t.empty()) - return; - - float t, sample, transmittance; - - if (!sampleWoodcock(rng, scene, ray, ray.t, t, sample, transmittance)) { - if (scatterIndex == 0) - return; // light is not directly visible - - // ambient light - Le += transmittance * vec3f(ambientLightIntensity); - - return; - } - - // new scattering event at sample point - scatterIndex++; - - if (scatterIndex > maxNumScatters) - return; - - const vec3f c = ray.org + t * ray.dir; - - Ray scatteringRay; - scatteringRay.t = range1f(0.f, inf); - scatteringRay.org = c; - scatteringRay.dir = uniformSampleSphere(1.f, rng.getFloats()); - - vec3f inscatteredLe; - integrate(rng, scene, scatteringRay, inscatteredLe, scatterIndex + 1); - - const vec4f sampleColorAndOpacity = sampleTransferFunction(scene, sample); - - const vec3f sigmaSSample = - sigmaSScale * vec3f(sampleColorAndOpacity) * sampleColorAndOpacity.w; - - Le = Le + sigmaSSample * inscatteredLe; - } - - vec3f DensityPathTracer::renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) - { - RNG rng(sampleID.z, (sampleID.w * sampleID.y) + sampleID.x); - vec3f Le; - integrate(rng, scene, ray, Le, 0); - return Le; - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/DensityPathTracer.h b/examples/interactive/renderers/DensityPathTracer.h deleted file mode 100644 index 427b7a1b..00000000 --- a/examples/interactive/renderers/DensityPathTracer.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "Renderer.h" - -namespace openvkl { - namespace examples { - - struct DensityPathTracer : public Renderer - { - DensityPathTracer(); - ~DensityPathTracer() override = default; - - void commit() override; - - vec3f renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) override; - - private: - bool sampleWoodcock(RNG &rng, - const Scene &scene, - const Ray &ray, - const range1f &hits, - float &t, - float &sample, - float &transmittance); - - void integrate( - RNG &rng, const Scene &scene, Ray &ray, vec3f &Le, int scatterIndex); - - // Data // - - float shutter{0.f}; - bool motionBlur{false}; - - float sigmaTScale{0.f}; - float sigmaSScale{0.f}; - int maxNumScatters{0}; - - float ambientLightIntensity{0.f}; - }; - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/DensityPathTracer.ispc b/examples/interactive/renderers/DensityPathTracer.ispc deleted file mode 100644 index e264d48e..00000000 --- a/examples/interactive/renderers/DensityPathTracer.ispc +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.ih" - -struct DensityPathTracer -{ - Renderer super; - - float time; - float shutter; - bool motionBlur; - float sigmaTScale; - float sigmaSScale; - int maxNumScatters; - float ambientLightIntensity; -}; - -inline vec3f cartesian(const float phi, - const float sinTheta, - const float cosTheta) -{ - float sinPhi, cosPhi; - sincos(phi, &sinPhi, &cosPhi); - return make_vec3f(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta); -} - -inline vec3f uniformSampleSphere(const float radius, const vec2f s) -{ - const float phi = two_pi * s.x; - const float cosTheta = radius * (1.f - 2.f * s.y); - const float sinTheta = 2.f * radius * sqrt(s.y * (1.f - s.y)); - return cartesian(phi, sinTheta, cosTheta); -} - -inline bool sampleWoodcock(DensityPathTracer *uniform self, - varying RandomTEA *const uniform rng, - const uniform Scene* uniform scene, - const Ray &ray, - const float tBox0, - const float tBox1, - float &t, - float &sample, - float &transmittance) -{ - t = tBox0; - - const float sigmaMax = self->sigmaTScale; - - while (true) { - vec2f randomNumbers = RandomTEA__getFloats(rng); - vec2f randomNumbers2 = RandomTEA__getFloats(rng); - - t = t + -logf(1.f - randomNumbers.x) / sigmaMax; - - if (t > tBox1) { - transmittance = 1.f; - return false; - } - - const vec3f c = ray.org + t * ray.dir; - float time = scene->time; - if (self->motionBlur) { - time = time + (randomNumbers2.x-0.5f) * self->shutter; - } - time = clamp(time, 0.f, 1.f); - sample = vklComputeSampleV(scene->sampler, - (varying vkl_vec3f * uniform) & c, - scene->attributeIndex, - &time); - - const vec4f sampleColorAndOpacity = - Renderer_sampleTransferFunction(scene, sample); - - // sigmaT must be mono-chromatic for Woodcock sampling - const float sigmaTSample = sigmaMax * sampleColorAndOpacity.w; - - if (randomNumbers.y < sigmaTSample / sigmaMax) - break; - } - - transmittance = 0.f; - return true; -} - -inline static void integrate(DensityPathTracer *uniform self, - varying RandomTEA *const uniform rng, - const uniform Scene* uniform scene, - const Ray &ray, - vec3f &Le, - int scatterIndex) -{ - // initialize emitted light to 0 - Le = make_vec3f(0.f); - - uniform vkl_box3f volumeBounds = vklGetBoundingBox(scene->volume); - const box3f *uniform bb = - (const uniform struct box3f *uniform) & volumeBounds; - - float tBox0, tBox1; - intersectBox(ray, *bb, tBox0, tBox1); - - if (tBox0 > tBox1) - return; - - float t, sample, transmittance; - - if (!sampleWoodcock( - self, rng, scene, ray, tBox0, tBox1, t, sample, transmittance)) { - if (scatterIndex == 0) - return; // light is not directly visible - - // ambient light - Le = Le + transmittance * make_vec3f(self->ambientLightIntensity); - - return; - } - - // new scattering event at sample point - scatterIndex++; - - if (scatterIndex > self->maxNumScatters) - return; - - const vec3f c = ray.org + t * ray.dir; - - Ray scatteringRay; - scatteringRay.tnear = 0.f; - scatteringRay.tfar = inf; - scatteringRay.org = c; - scatteringRay.dir = uniformSampleSphere(1.f, RandomTEA__getFloats(rng)); - - vec3f inscatteredLe; - integrate(self, - rng, - scene, - scatteringRay, - inscatteredLe, - scatterIndex + 1); - - const vec4f sampleColorAndOpacity = - Renderer_sampleTransferFunction(scene, sample); - - const vec3f sigmaSSample = self->sigmaSScale * - make_vec3f(sampleColorAndOpacity) * - sampleColorAndOpacity.w; - - Le = Le + sigmaSSample * inscatteredLe; -} - -vec3f DensityPathTracer_renderPixel(uniform Renderer *uniform _self, - const uniform Scene* uniform scene, - Ray &ray, - const vec2i &pixel, - const uniform int &frameID, - const uniform int &fbWidth) -{ - DensityPathTracer *uniform self = (DensityPathTracer * uniform) _self; - - RandomTEA rng_state; - varying RandomTEA *const uniform rng = &rng_state; - RandomTEA__Constructor(rng, frameID, (fbWidth * pixel.y) + pixel.x); - - vec3f Le; - integrate(self, rng, scene, ray, Le, 0); - return Le; -} - -export void *uniform DensityPathTracer_create() -{ - DensityPathTracer *uniform self = uniform new DensityPathTracer; - self->super.renderPixel = DensityPathTracer_renderPixel; - return self; -} - -export void DensityPathTracer_set(void *uniform _self, - const uniform float shutter, - const uniform bool motionBlur, - const uniform float sigmaTScale, - const uniform float sigmaSScale, - const uniform int maxNumScatters, - const uniform float ambientLightIntensity) -{ - DensityPathTracer *uniform self = (DensityPathTracer *)_self; - - self->shutter = shutter; - self->motionBlur = motionBlur; - self->sigmaTScale = sigmaTScale; - self->sigmaSScale = sigmaSScale; - self->maxNumScatters = maxNumScatters; - self->ambientLightIntensity = ambientLightIntensity; -} diff --git a/examples/interactive/renderers/HitIterator.cpp b/examples/interactive/renderers/HitIterator.cpp deleted file mode 100644 index 59b234d3..00000000 --- a/examples/interactive/renderers/HitIterator.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "HitIterator.h" -// ispc -#include "HitIterator_ispc.h" - -namespace openvkl { - namespace examples { - - HitIterator::HitIterator() - { - ispcEquivalent = ispc::HitIterator_create(); - } - - void HitIterator::commit() - { - Renderer::commit(); - } - - vec3f HitIterator::renderPixel(const Scene &scene, Ray &ray, const vec4i &) - { - vec3f color(0.f); - float alpha = 0.f; - - // create volume iterator - vkl_range1f tRange; - tRange.lower = ray.t.lower; - tRange.upper = ray.t.upper; - - void *hitIteratorBuffer = alloca(vklGetHitIteratorSize(scene.hitContext)); - void *shadowHitIteratorBuffer = - alloca(vklGetHitIteratorSize(scene.hitContext)); - - VKLHitIterator iterator = vklInitHitIterator(scene.hitContext, - (vkl_vec3f *)&ray.org, - (vkl_vec3f *)&ray.dir, - &tRange, - scene.time, - hitIteratorBuffer); - - // the current surface hit - VKLHit hit; - const float surfaceAlpha = 0.6f; - const float emission[] = {1.f, 0.8f}; - const vec3f lightDir[] = {normalize(vec3f(1.f, 1.f, 1.f)), - normalize(vec3f(1.f, 1.f, -1.f))}; - - while (vklIterateHit(iterator, &hit) && alpha < 0.99f) { - const vec3f c = ray.org + hit.t * ray.dir; - const vkl_vec3f grad = vklComputeGradient( - scene.sampler, (vkl_vec3f *)&c, scene.attributeIndex, scene.time); - vec3f N = normalize(vec3f(grad.x, grad.y, grad.z)); - if (std::isnan(N.x) || std::isnan(N.y) || std::isnan(N.z)) - N = vec3f(0.f); - // wi and wo both point away from the event. - const vec3f wi = normalize(-1.f * ray.dir); - const float ci = dot(N, wi); - - float illum = 0.f; - if (length(N) > 0) { - illum = 0.1f; // Ambient term. - // Passing through the surface? Include the surface we started on in - // alpha. - for (int i = 0; i < 2; ++i) { - const vec3f wo = lightDir[i]; - const float co = dot(N, wo); - - // Only test for shadow if we don't have to go through this surface. - if ((co > 0) == (ci > 0)) { - VKLHit shadowHit; - vkl_range1f tShadowRange; - tShadowRange.lower = hit.epsilon; - tShadowRange.upper = inf; - VKLHitIterator shadowIterator = - vklInitHitIterator(scene.hitContext, - (vkl_vec3f *)&c, - (vkl_vec3f *)&wo, - &tShadowRange, - scene.time, - shadowHitIteratorBuffer); - if (!vklIterateHit(shadowIterator, &shadowHit)) { - illum += abs(co) * emission[i]; // Lambertian surface shading. - } - } - } - } else { - // We have no normal, so do not attempt to shade. - illum = 1.f; - } - // Lambertian surface shading. - vec4f surfaceColorAndOpacity = - sampleTransferFunction(scene, hit.sample); - const vec3f albedo = vec3f(surfaceColorAndOpacity); - color = color + (1 - alpha) * illum * albedo; - alpha = alpha + (1.f - alpha) * surfaceAlpha; - } - - return color; - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/HitIterator.h b/examples/interactive/renderers/HitIterator.h deleted file mode 100644 index 149cd5e3..00000000 --- a/examples/interactive/renderers/HitIterator.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "Renderer.h" - -namespace openvkl { - namespace examples { - - struct HitIterator : public Renderer - { - HitIterator(); - ~HitIterator() override = default; - - void commit() override; - - vec3f renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) override; - }; - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/HitIterator.ispc b/examples/interactive/renderers/HitIterator.ispc deleted file mode 100644 index 74f0b55e..00000000 --- a/examples/interactive/renderers/HitIterator.ispc +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.ih" - -struct HitIterator -{ - Renderer super; -}; - -vec3f HitIterator_renderPixel(Renderer *uniform _self, - const uniform Scene *uniform scene, - Ray &ray, - const vec2i &, - const uniform int &, - const uniform int &) -{ - HitIterator *uniform self = (HitIterator * uniform) _self; - - vec3f color = make_vec3f(0.f); - float alpha = 0.f; - - // create volume iterator - vkl_range1f tRange; - tRange.lower = ray.tnear; - tRange.upper = ray.tfar; - - const float time = scene->time; - - void *uniform hitIteratorBuffer = - alloca(vklGetHitIteratorSizeV(scene->hitContext)); - void *uniform shadowHitIteratorBuffer = - alloca(vklGetHitIteratorSizeV(scene->hitContext)); - - VKLHitIterator iterator = - vklInitHitIteratorV(scene->hitContext, - (varying vkl_vec3f * uniform) & ray.org, - (varying vkl_vec3f * uniform) & ray.dir, - &tRange, - &time, - hitIteratorBuffer); - - // the current surface hit - VKLHit hit; - const float surfaceAlpha = 0.6f; - const uniform float emission[] = {1.f, 0.8f}; - const uniform vec3f lightDir[] = {normalize(make_vec3f(1.f, 1.f, 1.f)), - normalize(make_vec3f(1.f, 1.f, -1.f))}; - - while (vklIterateHitV(iterator, &hit) && alpha < 0.99f) { - const vec3f c = ray.org + hit.t * ray.dir; - const vkl_vec3f grad = - vklComputeGradientV(scene->sampler, - (varying vkl_vec3f * uniform) & c, - scene->attributeIndex, - &time); - vec3f N = normalize(make_vec3f(grad.x, grad.y, grad.z)); - if (isnan(N.x) || isnan(N.y) || isnan(N.z)) - N = make_vec3f(0.f); - // wi and wo both point away from the event. - const vec3f wi = normalize(-1.f * ray.dir); - const float ci = dot(N, wi); - - float illum = 0.f; - if (length(N) > 0) { - illum = 0.1f; // Ambient term. - // Passing through the surface? Include the surface we started on in - // alpha. - for (int i = 0; i < 2; ++i) { - const vec3f wo = lightDir[i]; - const float co = dot(N, wo); - - // Only test for shadow if we don't have to go through this surface. - if ((co > 0) == (ci > 0)) { - VKLHit shadowHit; - vkl_range1f tShadowRange; - tShadowRange.lower = hit.epsilon; - tShadowRange.upper = inf; - VKLHitIterator shadowIterator = - vklInitHitIteratorV(scene->hitContext, - (varying vkl_vec3f * uniform) & c, - (varying vkl_vec3f * uniform) & wo, - &tShadowRange, - &time, - shadowHitIteratorBuffer); - if (!vklIterateHitV(shadowIterator, &shadowHit)) { - illum += abs(co) * emission[i]; // Lambertian surface shading. - } - } - } - } else { - // We have no normal, so do not attempt to shade. - illum = 1.f; - } - - // Lambertian surface shading. - const vec4f surfaceColorAndOpacity = - Renderer_sampleTransferFunction(scene, hit.sample); - const vec3f albedo = make_vec3f(surfaceColorAndOpacity); - color = color + (1 - alpha) * illum * albedo; - alpha = alpha + (1.f - alpha) * surfaceAlpha; - } - - return color; -} - -export void *uniform HitIterator_create() -{ - HitIterator *uniform self = uniform new HitIterator; - self->super.renderPixel = HitIterator_renderPixel; - return self; -} diff --git a/examples/interactive/renderers/IntervalIteratorDebug.cpp b/examples/interactive/renderers/IntervalIteratorDebug.cpp deleted file mode 100644 index 786782ac..00000000 --- a/examples/interactive/renderers/IntervalIteratorDebug.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "IntervalIteratorDebug.h" -// ispc -#include "IntervalIteratorDebug_ispc.h" - -namespace openvkl { - namespace examples { - - IntervalIteratorDebug::IntervalIteratorDebug() - { - ispcEquivalent = ispc::IntervalIteratorDebug_create(); - } - - void IntervalIteratorDebug::commit() - { - Renderer::commit(); - - intervalColorScale = getParam("intervalColorScale", 4.f); - intervalOpacity = getParam("intervalOpacity", 0.25f); - firstIntervalOnly = getParam("firstIntervalOnly", false); - showIntervalBorders = getParam("showIntervalBorders", false); - - ispc::IntervalIteratorDebug_set(ispcEquivalent, - intervalColorScale, - intervalOpacity, - firstIntervalOnly, - showIntervalBorders); - } - - vec3f IntervalIteratorDebug::renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) - { - vec3f color(0.f); - float alpha = 0.f; - - vkl_range1f tRange; - tRange.lower = ray.t.lower; - tRange.upper = ray.t.upper; - - void *intervalIteratorBuffer = - alloca(vklGetIntervalIteratorSize(scene.intervalContext)); - VKLIntervalIterator iterator = - vklInitIntervalIterator(scene.intervalContext, - (vkl_vec3f *)&ray.org, - (vkl_vec3f *)&ray.dir, - &tRange, - scene.time, - intervalIteratorBuffer); - - VKLInterval interval; - - int intervalCount = 0; - - while (vklIterateInterval(iterator, &interval) && alpha < 0.99f) { - intervalCount++; - - const float dt = interval.tRange.upper - interval.tRange.lower; - - const float normalizedValue = - scene.tfValueRange.lower + - float(intervalCount) / intervalColorScale * - (scene.tfValueRange.upper - scene.tfValueRange.lower); - - vec4f sampleColorAndOpacity = - sampleTransferFunction(scene, normalizedValue); - - sampleColorAndOpacity.w = intervalOpacity; - - float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); - - sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; - - if (showIntervalBorders && dt < interval.nominalDeltaT) { - sampleColorAndOpacity = vec4f(1.f); - clampedOpacity = 1.f; - } - - color = color + (1.f - alpha) * vec3f(sampleColorAndOpacity); - alpha = alpha + (1.f - alpha) * clampedOpacity; - - if (firstIntervalOnly) { - break; - } - } - - return color; - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/IntervalIteratorDebug.h b/examples/interactive/renderers/IntervalIteratorDebug.h deleted file mode 100644 index e4ddd9ae..00000000 --- a/examples/interactive/renderers/IntervalIteratorDebug.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "Renderer.h" - -namespace openvkl { - namespace examples { - - struct IntervalIteratorDebug : public Renderer - { - IntervalIteratorDebug(); - ~IntervalIteratorDebug() override = default; - - void commit() override; - - vec3f renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) override; - - private: - float intervalColorScale{4.f}; - float intervalOpacity{0.25f}; - bool firstIntervalOnly{false}; - bool showIntervalBorders{false}; - }; - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/IntervalIteratorDebug.ispc b/examples/interactive/renderers/IntervalIteratorDebug.ispc deleted file mode 100644 index 060e400f..00000000 --- a/examples/interactive/renderers/IntervalIteratorDebug.ispc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.ih" - -struct IntervalIteratorDebug -{ - Renderer super; - float intervalColorScale; - float intervalOpacity; - bool firstIntervalOnly; - bool showIntervalBorders; -}; - -vec3f IntervalIteratorDebug_renderPixel(Renderer *uniform _self, - const uniform Scene *uniform scene, - Ray &ray, - const vec2i &pixel, - const uniform int &frameID, - const uniform int &fbWidth) -{ - IntervalIteratorDebug *uniform self = (IntervalIteratorDebug * uniform) _self; - - vec3f color = make_vec3f(0.f); - float alpha = 0.f; - - vkl_range1f tRange; - tRange.lower = ray.tnear; - tRange.upper = ray.tfar; - - const float time = scene->time; - - void *uniform intervalIteratorBuffer = - alloca(vklGetIntervalIteratorSizeV(scene->intervalContext)); - - VKLIntervalIterator iterator = - vklInitIntervalIteratorV(scene->intervalContext, - (varying vkl_vec3f * uniform) & ray.org, - (varying vkl_vec3f * uniform) & ray.dir, - &tRange, - &time, - intervalIteratorBuffer); - - VKLInterval interval; - - int intervalCount = 0; - - while (vklIterateIntervalV(iterator, &interval) && alpha < 0.99f) { - intervalCount++; - - const float dt = interval.tRange.upper - interval.tRange.lower; - - const float normalizedValue = - scene->tfValueRange.lower + - (float)intervalCount / self->intervalColorScale * - (scene->tfValueRange.upper - scene->tfValueRange.lower); - - vec4f sampleColorAndOpacity = - Renderer_sampleTransferFunction(scene, normalizedValue); - - sampleColorAndOpacity.w = self->intervalOpacity; - - float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); - - sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; - - if (self->showIntervalBorders && dt < interval.nominalDeltaT) { - sampleColorAndOpacity = make_vec4f(1.f); - clampedOpacity = 1.f; - } - - color = color + (1.f - alpha) * make_vec3f(sampleColorAndOpacity); - alpha = alpha + (1.f - alpha) * clampedOpacity; - - if (self->firstIntervalOnly) { - break; - } - } - - return color; -} - -export void *uniform IntervalIteratorDebug_create() -{ - IntervalIteratorDebug *uniform self = uniform new IntervalIteratorDebug; - self->super.renderPixel = IntervalIteratorDebug_renderPixel; - self->intervalColorScale = 4.f; - self->intervalOpacity = 0.25f; - self->firstIntervalOnly = false; - self->showIntervalBorders = false; - - return self; -} - -export void *uniform -IntervalIteratorDebug_set(void *uniform _self, - const uniform float intervalColorScale, - const uniform float intervalOpacity, - const uniform bool firstIntervalOnly, - const uniform bool showIntervalBorders) -{ - IntervalIteratorDebug *uniform self = (IntervalIteratorDebug * uniform) _self; - - self->intervalColorScale = intervalColorScale; - self->intervalOpacity = intervalOpacity; - self->firstIntervalOnly = firstIntervalOnly; - self->showIntervalBorders = showIntervalBorders; -} diff --git a/examples/interactive/renderers/RayMarchIterator.cpp b/examples/interactive/renderers/RayMarchIterator.cpp deleted file mode 100644 index d5e1477b..00000000 --- a/examples/interactive/renderers/RayMarchIterator.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "RayMarchIterator.h" -// ispc -#include "RayMarchIterator_ispc.h" - -namespace openvkl { - namespace examples { - - RayMarchIterator::RayMarchIterator() - { - ispcEquivalent = ispc::RayMarchIterator_create(); - } - - void RayMarchIterator::commit() - { - Renderer::commit(); - - samplingRate = getParam("samplingRate", 1.f); - - ispc::RayMarchIterator_set(ispcEquivalent, samplingRate); - } - - vec3f RayMarchIterator::renderPixel(const Scene &scene, - Ray &ray, - const vec4i &sampleID) - { - vec3f color(0.f); - float alpha = 0.f; - - // create volume iterator - vkl_range1f tRange; - tRange.lower = ray.t.lower; - tRange.upper = ray.t.upper; - - void *intervalIteratorBuffer = - alloca(vklGetIntervalIteratorSize(scene.intervalContext)); - VKLIntervalIterator iterator = - vklInitIntervalIterator(scene.intervalContext, - (vkl_vec3f *)&ray.org, - (vkl_vec3f *)&ray.dir, - &tRange, - scene.time, - intervalIteratorBuffer); - - // the current ray interval - VKLInterval interval; - - while (vklIterateInterval(iterator, &interval) && alpha < 0.99f) { - const float nominalSamplingDt = interval.nominalDeltaT / samplingRate; - - // initial sub interval, based on our renderer-defined sampling rate - // and the volume's nominal dt - box1f subInterval(interval.tRange.lower, - min(interval.tRange.lower + nominalSamplingDt, - interval.tRange.upper)); - - // integrate as long as we have valid sub intervals and are not - // fully opaque - while (subInterval.upper - subInterval.lower > 0.f && alpha < 0.99f) { - const float t = 0.5f * (subInterval.lower + subInterval.upper); - const float dt = subInterval.upper - subInterval.lower; - - // get volume sample - vec3f c = ray.org + t * ray.dir; - float sample = vklComputeSample( - scene.sampler, (vkl_vec3f *)&c, scene.attributeIndex, scene.time); - - // map through transfer function - vec4f sampleColorAndOpacity = sampleTransferFunction(scene, sample); - - // accumulate contribution - const float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); - - sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; - - color = color + (1.f - alpha) * vec3f(sampleColorAndOpacity); - alpha = alpha + (1.f - alpha) * clampedOpacity; - - // compute next sub interval - subInterval.lower = subInterval.upper; - subInterval.upper = - min(subInterval.lower + nominalSamplingDt, interval.tRange.upper); - } - } - - return color; - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/RayMarchIterator.h b/examples/interactive/renderers/RayMarchIterator.h deleted file mode 100644 index a1c96a22..00000000 --- a/examples/interactive/renderers/RayMarchIterator.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019-2020 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "Renderer.h" - -namespace openvkl { - namespace examples { - - struct RayMarchIterator : public Renderer - { - RayMarchIterator(); - ~RayMarchIterator() override = default; - - void commit() override; - - vec3f renderPixel(const Scene& scene, Ray &ray, const vec4i &sampleID) override; - - private: - float samplingRate{1.f}; - }; - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/RayMarchIterator.ispc b/examples/interactive/renderers/RayMarchIterator.ispc deleted file mode 100644 index 4d3883bb..00000000 --- a/examples/interactive/renderers/RayMarchIterator.ispc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.ih" - -struct RayMarchIterator -{ - Renderer super; - float samplingRate; -}; - -vec3f RayMarchIterator_renderPixel(Renderer *uniform _self, - const uniform Scene* uniform scene, - Ray &ray, - const vec2i &pixel, - const uniform int &frameID, - const uniform int &fbWidth) -{ - RayMarchIterator *uniform self = (RayMarchIterator * uniform) _self; - - vec3f color = make_vec3f(0.f); - float alpha = 0.f; - - // create volume iterator - vkl_range1f tRange; - tRange.lower = ray.tnear; - tRange.upper = ray.tfar; - - const float time = scene->time; - - void *uniform intervalIteratorBuffer = - alloca(vklGetIntervalIteratorSizeV(scene->intervalContext)); - - VKLIntervalIterator iterator = - vklInitIntervalIteratorV(scene->intervalContext, - (varying vkl_vec3f * uniform) & ray.org, - (varying vkl_vec3f * uniform) & ray.dir, - &tRange, - &time, - intervalIteratorBuffer); - - // the current ray interval - VKLInterval interval; - - while (vklIterateIntervalV(iterator, &interval) && alpha < 0.99f) { - const float nominalSamplingDt = interval.nominalDeltaT / self->samplingRate; - - // initial sub interval, based on our renderer-defined sampling rate - // and the volume's nominal dt - box1f subInterval = make_box1f( - interval.tRange.lower, - min(interval.tRange.lower + nominalSamplingDt, interval.tRange.upper)); - - // integrate as long as we have valid sub intervals and are not - // fully opaque - while (subInterval.upper - subInterval.lower > 0.f && alpha < 0.99f) { - const float t = 0.5f * (subInterval.lower + subInterval.upper); - const float dt = subInterval.upper - subInterval.lower; - - // get volume sample - vec3f c = ray.org + t * ray.dir; - float sample = vklComputeSampleV(scene->sampler, - (varying vkl_vec3f * uniform) & c, - scene->attributeIndex, - &time); - - // map through transfer function - vec4f sampleColorAndOpacity = - Renderer_sampleTransferFunction(scene, sample); - - // accumulate contribution - const float clampedOpacity = clamp(sampleColorAndOpacity.w * dt); - - sampleColorAndOpacity = sampleColorAndOpacity * clampedOpacity; - - color = color + (1.f - alpha) * make_vec3f(sampleColorAndOpacity); - alpha = alpha + (1.f - alpha) * clampedOpacity; - - // compute next sub interval - subInterval.lower = subInterval.upper; - subInterval.upper = - min(subInterval.lower + nominalSamplingDt, interval.tRange.upper); - } - } - - return color; -} - -export void *uniform RayMarchIterator_create() -{ - RayMarchIterator *uniform self = uniform new RayMarchIterator; - self->super.renderPixel = RayMarchIterator_renderPixel; - self->samplingRate = 1.f; - - return self; -} - -export void *uniform RayMarchIterator_set(void *uniform _self, - const uniform float samplingRate) -{ - RayMarchIterator *uniform self = (RayMarchIterator * uniform) _self; - - self->samplingRate = samplingRate; -} diff --git a/examples/interactive/renderers/Renderer.cpp b/examples/interactive/renderers/Renderer.cpp deleted file mode 100644 index 3674e5fb..00000000 --- a/examples/interactive/renderers/Renderer.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.h" -// std -#include -// rkcommon -#include "rkcommon/tasking/parallel_for.h" -// ispc -#include "Renderer_ispc.h" - -namespace openvkl { - namespace examples { - - Renderer::Renderer() = default; - - Renderer::~Renderer() - { - ispc::Renderer_freeRenderer(ispcEquivalent); - } - - void Renderer::commit() - { - spp = getParam("spp", 1); - } - - void Renderer::setCamera(const vec3f &pos, - const vec3f &dir, - const vec3f &up, - float aspect, - float fovy) - { - camPos = pos; - - dir_du = normalize(cross(dir, up)); - dir_dv = cross(dir_du, dir); - - float imgPlane_size_y = 2.f * tanf(fovy / 2.f * M_PI / 180.); - float imgPlane_size_x = imgPlane_size_y * aspect; - - dir_du *= imgPlane_size_x; - dir_dv *= imgPlane_size_y; - - dir_00 = dir - .5f * dir_du - .5f * dir_dv; - - ispc::Renderer_setCamera(ispcEquivalent, - (ispc::vec3f &)camPos, - (ispc::vec3f &)dir_00, - (ispc::vec3f &)dir_du, - (ispc::vec3f &)dir_dv); - } - - void Renderer::setPixelRange(const region2i &newPixelRange) - { - pixelRange = newPixelRange; - } - - void Renderer::setFrameSize(const vec2i &dims) - { - pixelIndices = index_sequence_2D(dims); - pixelRange = region2i(vec2i(0), vec2i(dims)); - - const auto numPixels = pixelIndices.total_indices(); - framebuffer.resize(numPixels); - accum_r.resize(numPixels); - accum_g.resize(numPixels); - accum_b.resize(numPixels); - - ispc::Renderer_setFrameBuffer(ispcEquivalent, - (ispc::vec3f *)framebuffer.data(), - accum_r.data(), - accum_g.data(), - accum_b.data()); - } - - vec2i Renderer::frameSize() const - { - return pixelIndices.dimensions(); - } - - void Renderer::resetAccumulation() - { - std::fill(accum_r.begin(), accum_r.end(), 0.f); - std::fill(accum_g.begin(), accum_g.end(), 0.f); - std::fill(accum_b.begin(), accum_b.end(), 0.f); - frameID = 0; - - ispc::Renderer_setFrameID(ispcEquivalent, frameID); - } - - const FrameBuffer &Renderer::frameBuffer() const - { - return framebuffer; - } - - void Renderer::renderFrame(const Scene& scene) - { - auto fbDims = pixelIndices.dimensions(); - auto rangeDims = pixelRange.size(); - - for (int i = 0; i < spp; ++i) { - float accumScale = 1.f / (frameID + 1); - - tasking::parallel_for(rangeDims.long_product(), [&](size_t i) { - auto pixel = vec2i(i % rangeDims.x, i / rangeDims.x) + pixelRange.lower; - - vec2f screen(pixel.x * rcp(float(fbDims.x)), - pixel.y * rcp(float(fbDims.y))); - - Ray ray = computeRay(screen); - const vec3f color = renderPixel(scene, ray, vec4i(pixel.x, pixel.y, frameID, fbDims.x)); - - auto fbIndex = pixelIndices.flatten(pixel); - float &ar = accum_r[fbIndex]; - float &ag = accum_g[fbIndex]; - float &ab = accum_b[fbIndex]; - - ar += color.x; - ag += color.y; - ab += color.z; - - framebuffer[fbIndex] = vec3f(ar, ag, ab) * accumScale; - - // linear to sRGB color space conversion - framebuffer[fbIndex] = vec3f(pow(framebuffer[fbIndex].x, 1.f / 2.2f), - pow(framebuffer[fbIndex].y, 1.f / 2.2f), - pow(framebuffer[fbIndex].z, 1.f / 2.2f)); - }); - - frameID++; - } - } - - void Renderer::renderFrame_ispc(const Scene& scene) - { - vec2i fbDims = pixelIndices.dimensions(); - auto rangeDims = pixelRange.size(); - ispc::vec2i fbDimsISPC{fbDims.x, fbDims.y}; - ispc::vec2i rangeDimsISPC{rangeDims.x, rangeDims.y}; - ispc::vec2i pixelOffsetISPC{pixelRange.lower.x, pixelRange.lower.y}; - - const size_t numJobs = - area(pixelRange) / ispc::Renderer_pixelsPerJob(); - - for (int i = 0; i < spp; ++i) { - float accumScale = 1.f / (frameID + 1); - - tasking::parallel_for(numJobs, [&](size_t i) { - ispc::Renderer_renderPixel(ispcEquivalent, - reinterpret_cast(&scene), - fbDimsISPC, - rangeDimsISPC, - pixelOffsetISPC, - frameID, - accumScale, - i); - }); - - frameID++; - } - } - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/Renderer.h b/examples/interactive/renderers/Renderer.h deleted file mode 100644 index f0bb7141..00000000 --- a/examples/interactive/renderers/Renderer.h +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// openvkl -#include "TransferFunction.h" -#include "Scene.h" -#include "openvkl/openvkl.h" -// rkcommon -#include "rkcommon/containers/AlignedVector.h" -#include "rkcommon/math/box.h" -#include "rkcommon/utility/ParameterizedObject.h" -#include "rkcommon/utility/multidim_index_sequence.h" -// std -#include -#include -#include - -#define USE_STD_RANDOM 0 - -namespace openvkl { - namespace examples { - - using namespace rkcommon; - using namespace rkcommon::math; - - using FrameBuffer = containers::AlignedVector; - using ColorChannel = containers::AlignedVector; - - struct Ray - { - vec3f org; - vec3f dir; - range1f t; - }; - - struct Renderer : public utility::ParameterizedObject - { - Renderer(); - - virtual ~Renderer(); - - // Parameters // - - virtual void commit(); - - // Camera setup // - - void setCamera(const vec3f &pos, - const vec3f &dir, - const vec3f &up, - float aspect, - float fovy = 60.f); - - // Framebuffer access // - - void setFrameSize(const vec2i &dims); - void setPixelRange(const region2i &pixelRange); - vec2i frameSize() const; - void resetAccumulation(); - const FrameBuffer &frameBuffer() const; - - // Render a frame // - - void renderFrame(const Scene& scene); - void renderFrame_ispc(const Scene& scene); - - protected: - virtual vec3f renderPixel(const Scene& scene, Ray &ray, const vec4i &sampleID) = 0; - - Ray computeRay(const vec2f &screenCoords) const; - vec4f sampleTransferFunction(const Scene& scene, float value) const; - - // Camera data // - - vec3f camPos; - vec3f dir_00; - vec3f dir_du; - vec3f dir_dv; - - // Frame data // - - index_sequence_2D pixelIndices{vec2i(0)}; - region2i pixelRange{vec2i(0),vec2i(0)}; - FrameBuffer framebuffer; - ColorChannel accum_r; - ColorChannel accum_g; - ColorChannel accum_b; - int spp{1}; - int frameID{0}; - - // Renderer data // - void *ispcEquivalent{nullptr}; - }; - - // Inlined definitions //////////////////////////////////////////////////// - - inline Ray Renderer::computeRay(const vec2f &screenCoords) const - { - vec3f org = camPos; - vec3f dir = dir_00 + screenCoords.x * dir_du + screenCoords.y * dir_dv; - - Ray ray; - - ray.org = org; - ray.dir = normalize(dir); - ray.t = range1f(0.f, rkcommon::inf); - - return ray; - } - - inline vec4f Renderer::sampleTransferFunction(const Scene& scene, float value) const - { - vec4f colorAndOpacity{0.f}; - - if (std::isnan(value) || scene.tfNumColorsAndOpacities == 0) { - return colorAndOpacity; - } - - if (value <= scene.tfValueRange.lower) { - return scene.tfColorsAndOpacities[0]; - } - - if (value >= scene.tfValueRange.upper) { - return scene.tfColorsAndOpacities[scene.tfNumColorsAndOpacities-1]; - } - - // map the value into the range [0, size - 1] - value = (value - scene.tfValueRange.lower) / - (scene.tfValueRange.upper - - scene.tfValueRange.lower) * (scene.tfNumColorsAndOpacities - 1.f); - - // index and fractional offset - const int index = floor(value); - const float remainder = value - index; - - // the final interpolated value - return ((1.f - remainder) * scene.tfColorsAndOpacities[index] + - remainder * - scene.tfColorsAndOpacities[min( - index + 1, - int(scene.tfNumColorsAndOpacities - 1))]); - } - - /////////////////////////////////////////////////////////////////////////// - // RNG //////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - -#if USE_STD_RANDOM - static float getRandomUniform() - { - static thread_local std::minstd_rand rng; - static std::uniform_real_distribution distribution{0.f, 1.f}; - - return distribution(rng); - } - - struct RandomSTD - { - template - RandomSTD(const T &, const T &) {} - - vec2f getFloats() - { - return vec2f{getRandomUniform(), getRandomUniform()}; - } - }; - - using RNG = RandomSTD; -#else - - // TEA - Random numbers based on Tiny Encryption Algorithm // - - template - inline void tea8(T &v0, T &v1) - { - T sum{0}; - - for (int i = 0; i < NUM_ROUNDS; i++) { - sum += 0x9e3779b9; - v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + sum) ^ ((v1 >> 5) + 0xc8013ea4); - v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7e95761e); - } - } - - template - struct RandomTEA - { - RandomTEA(const T &idx, const T &seed) : v0(idx), v1(seed) {} - - vec2f getFloats() - { - tea8(v0, v1); - const float tofloat = 2.3283064365386962890625e-10f; // 1/2^32 - return vec2f{v0 * tofloat, v1 * tofloat}; - } - - T v0, v1; - }; - - using RNG = RandomTEA; -#endif - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/renderers/Renderer.ih b/examples/interactive/renderers/Renderer.ih deleted file mode 100644 index 274c630a..00000000 --- a/examples/interactive/renderers/Renderer.ih +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "rkcommon/math/box.ih" - -#include "Scene.h" - -// openvkl -#include "openvkl/openvkl.isph" - -struct Ray -{ - vec3f org; - vec3f dir; - float tnear; - float tfar; -}; - -struct Renderer -{ - vec3f camPos; - vec3f dir_00; - vec3f dir_du; - vec3f dir_dv; - - vec3f *framebuffer; - float *accum_r; - float *accum_g; - float *accum_b; - int frameID; - - vec3f (*uniform renderPixel)(uniform Renderer *uniform self, - const uniform Scene *uniform scene, - Ray &ray, - const vec2i &pixel, - const uniform int &frameID, - const uniform int &fbWidth); -}; - -inline Ray Renderer_computeRay(uniform Renderer *uniform self, - const vec2f &screenCoords) -{ - vec3f dir = self->dir_00 + screenCoords.x * self->dir_du + - screenCoords.y * self->dir_dv; - - Ray ray; - - ray.org = self->camPos; - ray.dir = normalize(dir); - ray.tnear = 0.f; - ray.tfar = inf; - - return ray; -} - -inline vec4f Renderer_sampleTransferFunction(const uniform Scene *uniform scene, - float value) -{ - vec4f colorAndOpacity = make_vec4f(0.f); - - if (isnan(value) || scene->tfNumColorsAndOpacities == 0) { - return colorAndOpacity; - } - - if (value <= scene->tfValueRange.lower) { - return scene->tfColorsAndOpacities[0]; - } - - if (value >= scene->tfValueRange.upper) { - return scene->tfColorsAndOpacities[scene->tfNumColorsAndOpacities - 1]; - } - - // map the value into the range [0, size - 1] - value = (value - scene->tfValueRange.lower) / - (scene->tfValueRange.upper - scene->tfValueRange.lower) * - (scene->tfNumColorsAndOpacities - 1.f); - - // index and fractional offset - const int index = floor(value); - const float remainder = value - index; - - // the final interpolated value - return ((1.f - remainder) * scene->tfColorsAndOpacities[index] + - remainder * - scene->tfColorsAndOpacities[min( - ((int32)(index + 1)), - ((uniform int32)(scene->tfNumColorsAndOpacities - 1)))]); -} - -inline void intersectBox(const Ray &ray, - const uniform box3f &box, - float &tnear, - float &tfar) -{ - const vec3f mins = (box.lower - ray.org) * rcp_safe(ray.dir); - const vec3f maxs = (box.upper - ray.org) * rcp_safe(ray.dir); - tnear = reduce_max(make_vec4f(min(mins, maxs), ray.tnear)); - tfar = reduce_min(make_vec4f(max(mins, maxs), ray.tfar)); -} - -/////////////////////////////////////////////////////////////////////////////// -// TEA - Random numbers based on Tiny Encryption Algorithm //////////////////// -/////////////////////////////////////////////////////////////////////////////// - -inline void tea8(unsigned int &_v0, unsigned int &_v1) -{ - unsigned int v0 = _v0; // Operate on registers to avoid slowdown! - unsigned int v1 = _v1; - unsigned int sum = 0; - - for (uniform int i = 0; i < 8; i++) { // just 8 instead of 32 rounds - sum += 0x9e3779b9; - v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + sum) ^ ((v1 >> 5) + 0xc8013ea4); - v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7e95761e); - } - - _v0 = v0; - _v1 = v1; -} - -struct RandomTEA -{ - unsigned int v0, v1; -}; - -inline void RandomTEA__Constructor(varying RandomTEA *uniform this, - const unsigned int idx, - const unsigned int seed) -{ - this->v0 = idx; - this->v1 = seed; -} - -inline varying vec2f RandomTEA__getFloats(varying RandomTEA *uniform this) -{ - tea8(this->v0, this->v1); - const float tofloat = 2.3283064365386962890625e-10f; // 1/2^32 - return make_vec2f(this->v0 * tofloat, this->v1 * tofloat); -} diff --git a/examples/interactive/renderers/Renderer.ispc b/examples/interactive/renderers/Renderer.ispc deleted file mode 100644 index 73365d80..00000000 --- a/examples/interactive/renderers/Renderer.ispc +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "Renderer.ih" - -export uniform int Renderer_pixelsPerJob() -{ - return programCount; -} - -export void Renderer_setCamera(void *uniform _self, - const uniform vec3f &camPos, - const uniform vec3f &dir_00, - const uniform vec3f &dir_du, - const uniform vec3f &dir_dv) -{ - Renderer *uniform self = (Renderer * uniform) _self; - - self->camPos = camPos; - self->dir_00 = dir_00; - self->dir_du = dir_du; - self->dir_dv = dir_dv; -} - -export void Renderer_setFrameBuffer(void *uniform _self, - vec3f *uniform framebuffer, - float *uniform accum_r, - float *uniform accum_g, - float *uniform accum_b) -{ - Renderer *uniform self = (Renderer * uniform) _self; - - self->framebuffer = framebuffer; - - self->accum_r = accum_r; - self->accum_g = accum_g; - self->accum_b = accum_b; -} - -export void Renderer_setFrameID(void *uniform _self, uniform int frameID) -{ - Renderer *uniform self = (Renderer * uniform) _self; - - self->frameID = frameID; -} - -export void Renderer_renderPixel(void *uniform _self, - const uniform Scene* uniform scene, - const uniform vec2i &fbDims, - const uniform vec2i &rangeDims, - const uniform vec2i &pixelOffset, - const uniform int frameID, - const uniform float accumScale, - const uniform int _taskID) -{ - Renderer *uniform self = (Renderer * uniform) _self; - - const int pixelID = _taskID * programCount + programIndex; - - vec2i pixel = make_vec2i(pixelOffset.x + (pixelID % rangeDims.x), - pixelOffset.y + (pixelID / rangeDims.x)); - - if (pixel.x >= fbDims.x) - return; - - const vec2f screen = make_vec2f(pixel.x * rcp((uniform float)fbDims.x), - pixel.y * rcp((uniform float)fbDims.y)); - - Ray ray = Renderer_computeRay(self, screen); - const vec3f color = self->renderPixel(self, scene, ray, pixel, frameID, fbDims.x); - - const int fbIndex = pixel.x + fbDims.x * pixel.y; - float ar = self->accum_r[fbIndex]; - float ag = self->accum_g[fbIndex]; - float ab = self->accum_b[fbIndex]; - - ar += color.x; - ag += color.y; - ab += color.z; - - self->accum_r[fbIndex] = ar; - self->accum_g[fbIndex] = ag; - self->accum_b[fbIndex] = ab; - - self->framebuffer[fbIndex] = make_vec3f(ar, ag, ab) * accumScale; - - // linear to sRGB color space conversion - self->framebuffer[fbIndex] = - make_vec3f(pow(self->framebuffer[fbIndex].x, 1.f / 2.2f), - pow(self->framebuffer[fbIndex].y, 1.f / 2.2f), - pow(self->framebuffer[fbIndex].z, 1.f / 2.2f)); -} - -export void Renderer_freeRenderer(void *uniform self) -{ - delete self; -} diff --git a/examples/interactive/renderers/Scene.h b/examples/interactive/renderers/Scene.h deleted file mode 100644 index 95f281dd..00000000 --- a/examples/interactive/renderers/Scene.h +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2020-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#if defined(ISPC) - -#include "openvkl/openvkl.isph" - -#endif // defined(ISPC) - -#if defined(__cplusplus) - -#include "TransferFunction.h" -#include "apps/AppInit.h" -#include "openvkl/openvkl.h" - -namespace openvkl { - namespace examples { - -#endif // defined(__cplusplus) - - /* - * This object stores all scene data. Renderers - * read this to produce pixels. - */ - struct Scene - { - /* - * Our examples display a single volume. - */ - VKLVolume volume; - VKLSampler sampler; - VKLIntervalIteratorContext intervalContext; - VKLHitIteratorContext hitContext; - unsigned int attributeIndex; - float time; - - /* - * Shading is done through a transfer function. - */ - box1f tfValueRange; - unsigned int tfNumColorsAndOpacities; - const vec4f *tfColorsAndOpacities; - -#if defined(__cplusplus) - Scene() - : volume(nullptr), - sampler(nullptr), - intervalContext(nullptr), - hitContext(nullptr), - attributeIndex(0), - time(0.f), - tfNumColorsAndOpacities(0), - tfColorsAndOpacities(nullptr) - { - } - - ~Scene() - { - if (intervalContext) { - vklRelease(intervalContext); - } - if (hitContext) { - vklRelease(hitContext); - } - if (sampler) { - vklRelease(sampler); - } - } - - void updateIntervalIteratorContextValueRanges( - const TransferFunction &transferFunction) - { - // set interval context value ranges based on transfer function positive - // opacity intervals, if we have any - VKLData valueRangesData = nullptr; - - std::vector valueRanges = - transferFunction.getPositiveOpacityValueRanges(); - - if (!valueRanges.empty()) { - valueRangesData = vklNewData(getOpenVKLDevice(), - valueRanges.size(), - VKL_BOX1F, - valueRanges.data()); - } - - vklSetData(intervalContext, "valueRanges", valueRangesData); - - if (valueRangesData) { - vklRelease(valueRangesData); - } - - vklCommit(intervalContext); - } - - void updateHitIteratorContextValues(const std::vector &isoValues) - { - // if we have isovalues, set these values on the context - VKLData valuesData = nullptr; - - if (!isoValues.empty()) { - valuesData = vklNewData(getOpenVKLDevice(), - isoValues.size(), - VKL_FLOAT, - isoValues.data()); - } - - vklSetData(hitContext, "values", valuesData); - - if (valuesData) { - vklRelease(valuesData); - } - - vklCommit(hitContext); - } - - void updateAttributeIndex(unsigned int attributeIndex) - { - this->attributeIndex = attributeIndex; - - vklSetInt(intervalContext, "attributeIndex", attributeIndex); - vklCommit(intervalContext); - - vklSetInt(hitContext, "attributeIndex", attributeIndex); - vklCommit(hitContext); - } - - void updateVolume(VKLVolume volume) - { - if (sampler) { - vklRelease(sampler); - sampler = nullptr; - } - - if (intervalContext) { - vklRelease(intervalContext); - intervalContext = nullptr; - } - - if (hitContext) { - vklRelease(hitContext); - hitContext = nullptr; - } - - this->volume = volume; - - if (!this->volume) - return; - - sampler = vklNewSampler(volume); - vklCommit(sampler); - - intervalContext = vklNewIntervalIteratorContext(sampler); - vklCommit(intervalContext); - - hitContext = vklNewHitIteratorContext(sampler); - vklCommit(hitContext); - } -#endif // defined(__cplusplus) - }; - -#if defined(__cplusplus) - - } // namespace examples -} // namespace openvkl - -#endif // defined(__cplusplus) diff --git a/examples/interactive/vklBenchmark.cpp b/examples/interactive/vklBenchmark.cpp index b75dbd05..0c19fd3b 100644 --- a/examples/interactive/vklBenchmark.cpp +++ b/examples/interactive/vklBenchmark.cpp @@ -1,18 +1,22 @@ // Copyright 2019-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 -#include "Renderer_ispc.h" -#include "window/VKLWindow.h" +#include "Renderer_ispc.h" // for Renderer_pixelsPerJob() +#include "renderer/Renderer.h" +#include "renderer/Scene.h" + // openvkl_testing #include "openvkl_testing.h" // google benchmark #include "benchmark/benchmark.h" // std +#include #include #include // rkcommon #include "rkcommon/common.h" #include "rkcommon/math/box.h" +#include "rkcommon/utility/SaveImage.h" using namespace openvkl::examples; using namespace openvkl::testing; @@ -20,19 +24,19 @@ using namespace rkcommon::math; using openvkl::testing::WaveletVdbVolumeFloat; static bool rendererIsCompatibleWithDevice(const std::string &rendererType, - bool useISPC, std::string &errorString) { // ISPC renderers that use iterator APIs must match width with the // instantiated VKL device - if (useISPC && rendererType.find("iterator") != std::string::npos) { + if (rendererType.find("ispc") && + rendererType.find("iterator") != std::string::npos) { const int deviceNativeSIMDWidth = vklGetNativeSIMDWidth(getOpenVKLDevice()); const int ispcRendererSIMDWidth = ispc::Renderer_pixelsPerJob(); if (deviceNativeSIMDWidth != ispcRendererSIMDWidth) { std::stringstream ss; - ss << rendererType << " (useISPC = " << useISPC - << ") is not compatible with the current VKL device (device width = " + ss << rendererType + << " is not compatible with the current VKL device (device width = " << deviceNativeSIMDWidth << ", renderer width = " << ispcRendererSIMDWidth << ")"; @@ -50,160 +54,116 @@ static bool rendererIsCompatibleWithDevice(const std::string &rendererType, // benchmark runs (e.g. `state.SkipWithError()`), but with those benchmark // results to still have JSON output populated, which we do not want. This // approach avoids that. -#define BENCHMARK_CAPTURE_IF_COMPATIBLE(FUNC, \ - TEST_CASE_NAME, \ - RENDERER_TYPE, \ - WINDOW_SIZE, \ - VOLUME_DIMENSION, \ - USE_ISPC) \ - { \ - std::string errorString; \ - if (!rendererIsCompatibleWithDevice( \ - RENDERER_TYPE, USE_ISPC, errorString)) { \ - std::cerr << "skipping benchmark capture: " << #FUNC << ", " \ - << #TEST_CASE_NAME << "\n\t" << errorString << std::endl; \ - } else { \ - BENCHMARK_CAPTURE(FUNC, \ - TEST_CASE_NAME, \ - RENDERER_TYPE, \ - WINDOW_SIZE, \ - VOLUME_DIMENSION, \ - USE_ISPC); \ - } \ +#define BENCHMARK_CAPTURE_IF_COMPATIBLE( \ + FUNC, TEST_CASE_NAME, RENDERER_TYPE, WINDOW_SIZE, VOLUME_DIMENSION) \ + { \ + std::string errorString; \ + if (!rendererIsCompatibleWithDevice(RENDERER_TYPE, errorString)) { \ + std::cerr << "skipping benchmark capture: " << #FUNC << ", " \ + << #TEST_CASE_NAME << "\n\t" << errorString << std::endl; \ + } else { \ + BENCHMARK_CAPTURE( \ + FUNC, TEST_CASE_NAME, RENDERER_TYPE, WINDOW_SIZE, VOLUME_DIMENSION); \ + } \ } -static void setupSceneDefaults(Scene &scene) +static void run_benchmark(benchmark::State &state, + const std::string &field, + const std::string &volumeType, + const std::string &rendererType, + const vec2i &resolution, + int volumeDimension) { - if (!scene.volume) { - throw std::runtime_error("scene must have an active volume"); - } - - // set a default transfer function - static TransferFunction transferFunction; - - scene.tfValueRange = box1f(0.f, 1.f); - scene.tfColorsAndOpacities = transferFunction.colorsAndOpacities.data(); - scene.tfNumColorsAndOpacities = transferFunction.colorsAndOpacities.size(); - - // and default iterator contexts - scene.updateIntervalIteratorContextValueRanges(transferFunction); - scene.updateHitIteratorContextValues(std::vector{-1.f, 0.f, 1.f}); -} - -static void render_wavelet_structured_regular(benchmark::State &state, - const std::string &rendererType, - const vec2i &windowSize, - int volumeDimension, - bool useISPC) -{ - auto proceduralVolume = - rkcommon::make_unique>( - vec3i(volumeDimension), vec3f(-1.f), vec3f(2.f / volumeDimension)); + const std::list args = {"vklBenchmark", + "-sync", + "-volumeType", + volumeType, + "-field", + field, + "-framebufferSize", + std::to_string(resolution.x), + std::to_string(resolution.y), + "-gridDimensions", + std::to_string(volumeDimension), + std::to_string(volumeDimension), + std::to_string(volumeDimension)}; Scene scene; - scene.updateVolume(proceduralVolume->getVKLVolume(getOpenVKLDevice())); + scene.parseCommandLine(args); + + auto &scheduler = scene.scheduler; + auto &volume = scene.volume; - setupSceneDefaults(scene); + volume.updateVKLObjects(); + scene.camera->fitToScreen(volume.getBounds()); + scene.camera.incrementVersion(); - auto window = - rkcommon::make_unique(windowSize, scene, rendererType); + auto rendererPtr = scene.createRenderer(rendererType); + assert(rendererPtr); - window->setUseISPC(useISPC); + Renderer &renderer = *(rendererPtr.get()); + // This call will resize the framebuffer to our desired output + // resolution. + renderer.getFramebuffer(resolution.x, resolution.y); + scheduler.start(renderer); for (auto _ : state) { - window->render(); + scheduler.renderFrame(renderer); } + scheduler.stop(renderer); // enables rates in report output state.SetItemsProcessed(state.iterations()); - // save image on completion of benchmark; note we apparently have no way to - // get the formal benchmark name, so we'll create one here - static int ppmCounter = 0; - std::stringstream ss; - ss << "render_wavelet_structured_regular" << rendererType << "_" << ppmCounter - << ".ppm"; - window->savePPM(ss.str()); - ppmCounter++; + static size_t ctr = 0; + std::ostringstream os; + os << std::setw(4) << std::setfill('0') << ctr++ << "_" << field + << "_" << volumeType << "_" << rendererType << ".ppm"; + + const auto &framebuffer = renderer.getFramebuffer(resolution.x, resolution.y); + const auto &fb = framebuffer.getFrontBuffer(); + rkcommon::utility::writePFM( + os.str(), fb.getWidth(), fb.getHeight(), fb.getRgba()); +} + +static void render_wavelet_structured_regular(benchmark::State &state, + const std::string &rendererType, + const vec2i &windowSize, + int volumeDimension) +{ + run_benchmark(state, + "wavelet", + "structuredRegular", + rendererType, + windowSize, + volumeDimension); } static void render_wavelet_vdb(benchmark::State &state, const std::string &rendererType, const vec2i &windowSize, - int volumeDimension, - bool useISPC) + int volumeDimension) { - auto proceduralVolume = rkcommon::make_unique( - getOpenVKLDevice(), - vec3i(volumeDimension), - vec3f(-1.f), - vec3f(2.f / volumeDimension)); - - Scene scene; - scene.updateVolume(proceduralVolume->getVKLVolume(getOpenVKLDevice())); - - setupSceneDefaults(scene); - - auto window = - rkcommon::make_unique(windowSize, scene, rendererType); - - window->setUseISPC(useISPC); - - for (auto _ : state) { - window->render(); - } - - // enables rates in report output - state.SetItemsProcessed(state.iterations()); - - // save image on completion of benchmark; note we apparently have no way to - // get the formal benchmark name, so we'll create one here - static int ppmCounter = 0; - std::stringstream ss; - ss << "render_wavelet_vdb" << rendererType << "_" << ppmCounter << ".ppm"; - window->savePPM(ss.str()); - ppmCounter++; + run_benchmark(state, + "wavelet", + "vdb", + rendererType, + windowSize, + volumeDimension); } static void render_wavelet_unstructured_hex(benchmark::State &state, const std::string &rendererType, const vec2i &windowSize, - int volumeDimension, - bool useISPC) + int volumeDimension) { - auto proceduralVolume = - rkcommon::make_unique( - vec3i(volumeDimension), - vec3f(-1.f), - vec3f(2.f / volumeDimension), - VKL_HEXAHEDRON, - false); - - Scene scene; - scene.updateVolume(proceduralVolume->getVKLVolume(getOpenVKLDevice())); - - setupSceneDefaults(scene); - - auto window = - rkcommon::make_unique(windowSize, scene, rendererType); - - window->setUseISPC(useISPC); - - for (auto _ : state) { - window->render(); - } - - // enables rates in report output - state.SetItemsProcessed(state.iterations()); - - // save image on completion of benchmark; note we apparently have no way to - // get the formal benchmark name, so we'll create one here - static int ppmCounter = 0; - std::stringstream ss; - ss << "render_wavelet_unstructured_hex" << rendererType << "_" << ppmCounter - << ".ppm"; - window->savePPM(ss.str()); - ppmCounter++; + // Note: this is a hex volume because hex cells are the default! + run_benchmark(state, + "wavelet", + "unstructured", + rendererType, + windowSize, + volumeDimension); } // based on BENCHMARK_MAIN() macro from benchmark.h @@ -216,129 +176,111 @@ int main(int argc, char **argv) density_pathtracer / 512 / scalar, "density_pathtracer", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_structured_regular, density_pathtracer / 512 / ispc, - "density_pathtracer", + "density_pathtracer_ispc", vec2i(1024), - 512, - true); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_structured_regular, hit_iterator / 512 / scalar, "hit_iterator", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_structured_regular, hit_iterator / 512 / ispc, - "hit_iterator", + "hit_iterator_ispc", vec2i(1024), - 512, - true); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_structured_regular, ray_march_iterator / 512 / scalar, "ray_march_iterator", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_structured_regular, ray_march_iterator / 512 / ispc, - "ray_march_iterator", + "ray_march_iterator_ispc", vec2i(1024), - 512, - true); + 512); // wavelet vdb BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, density_pathtracer / 512 / scalar, "density_pathtracer", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, density_pathtracer / 512 / ispc, - "density_pathtracer", + "density_pathtracer_ispc", vec2i(1024), - 512, - true); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, hit_iterator / 512 / scalar, "hit_iterator", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, hit_iterator / 512 / ispc, - "hit_iterator", + "hit_iterator_ispc", vec2i(1024), - 512, - true); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, ray_march_iterator / 512 / scalar, "ray_march_iterator", vec2i(1024), - 512, - false); + 512); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_vdb, ray_march_iterator / 512 / ispc, - "ray_march_iterator", + "ray_march_iterator_ispc", vec2i(1024), - 512, - true); + 512); // wavelet unstructured BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - density_pathtracer / 512 / scalar, + density_pathtracer / 128 / scalar, "density_pathtracer", vec2i(1024), - 512, - false); + 128); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - density_pathtracer / 512 / ispc, - "density_pathtracer", + density_pathtracer / 128 / ispc, + "density_pathtracer_ispc", vec2i(1024), - 512, - true); + 128); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - hit_iterator / 512 / scalar, + hit_iterator / 128 / scalar, "hit_iterator", vec2i(1024), - 512, - false); + 128); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - hit_iterator / 512 / ispc, - "hit_iterator", + hit_iterator / 128 / ispc, + "hit_iterator_ispc", vec2i(1024), - 512, - true); + 128); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - ray_march_iterator / 512 / scalar, + ray_march_iterator / 128 / scalar, "ray_march_iterator", vec2i(1024), - 512, - false); + 128); BENCHMARK_CAPTURE_IF_COMPATIBLE(render_wavelet_unstructured_hex, - ray_march_iterator / 512 / ispc, - "ray_march_iterator", + ray_march_iterator / 128 / ispc, + "ray_march_iterator_ispc", vec2i(1024), - 512, - true); + 128); ::benchmark::Initialize(&argc, argv); if (::benchmark::ReportUnrecognizedArguments(argc, argv)) diff --git a/examples/interactive/vklExamples.cpp b/examples/interactive/vklExamples.cpp index 675673c4..37072edb 100644 --- a/examples/interactive/vklExamples.cpp +++ b/examples/interactive/vklExamples.cpp @@ -1,1286 +1,39 @@ -// Copyright 2019-2021 Intel Corporation +// Copyright 2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 -#include "window/GLFWVKLWindow.h" -#include "window/TransferFunctionWidget.h" -// openvkl_testing -#include "openvkl/utility/vdb/InnerNodes.h" -#include "openvkl_testing.h" -// imgui -#include -// std -#include -#include -#include -#include +#include "BatchApplication.h" +#include "InteractiveApplication.h" +#include "renderer/Scene.h" -using namespace rkcommon; -using namespace openvkl::testing; using namespace openvkl::examples; -struct ViewerParams +int main(int argc, char **argv) { - vec3f gridOrigin{rkcommon::nan}; - vec3f gridSpacing{rkcommon::nan}; - vec3i dimensions{128}; - vec2i windowSize{1024}; - region2i pixelRange{vec2i(0, 0), vec2i(0, 0)}; - bool cmdlinePixelRange = false; - std::string rendererType{"density_pathtracer"}; - std::string gridType{"structuredRegular"}; - std::string voxelTypeString{"float"}; - std::string filename; - std::string fileNameOut; - std::string field; - std::vector motionBlurUnstructuredTimeSamples{ - 0.f, 0.15f, 0.3f, 0.65f, 0.9f, 1.0f}; - range1f initialValueRange{0.f, 1.f}; - VKLDataType voxelType{VKL_FLOAT}; - VKLFilter filter{VKL_FILTER_TRILINEAR}; - VKLFilter gradientFilter{VKL_FILTER_TRILINEAR}; - size_t numParticles{1000}; - float intervalResolutionHint{0.5f}; - int maxSamplingDepth = VKL_VDB_NUM_LEVELS - 1; - uint8_t motionBlurStructuredNumTimesteps{6}; - bool multiAttribute{false}; - bool motionBlurStructured{false}; - bool motionBlurUnstructured{false}; - bool haveFilter{false}; - bool haveVdb{false}; - bool disableVSync{false}; - bool interactive{true}; - bool useISPC{true}; - std::string innerNodeOutput; - int innerNodeMaxDepth{1}; - float background{VKL_BACKGROUND_UNDEFINED}; -}; - -bool addSamplingRateUI(GLFWVKLWindow &window) -{ - auto &renderer = window.currentRenderer(); - - static float samplingRate = 1.f; - if (ImGui::SliderFloat("samplingRate", &samplingRate, 0.01f, 4.f)) { - renderer.setParam("samplingRate", samplingRate); - renderer.commit(); - return true; - } - - return false; -} - -bool addPathTracerUI(GLFWVKLWindow &window, Scene &scene) -{ - auto &renderer = window.currentRenderer(); - - bool changed = false; - - static bool motionBlur = false; - if (ImGui::Checkbox("motion blur", &motionBlur)) { - renderer.setParam("motionBlur", motionBlur); - changed = true; - } - - static float shutter = 0.f; - if (motionBlur && ImGui::SliderFloat("shutter", &shutter, 0.f, 1.f)) { - renderer.setParam("shutter", shutter); - changed = true; - } - - static float sigmaTScale = 1.f; - if (ImGui::SliderFloat("sigmaTScale", &sigmaTScale, 0.001f, 100.f)) { - renderer.setParam("sigmaTScale", sigmaTScale); - changed = true; - } - - static float sigmaSScale = 1.f; - if (ImGui::SliderFloat("sigmaSScale", &sigmaSScale, 0.01f, 1.f)) { - renderer.setParam("sigmaSScale", sigmaSScale); - changed = true; - } - - static int maxNumScatters = 1; - if (ImGui::SliderInt("maxNumScatters", &maxNumScatters, 1, 32)) { - renderer.setParam("maxNumScatters", maxNumScatters); - changed = true; - } - - static float ambientLightIntensity = 1.f; - if (ImGui::SliderFloat( - "ambientLightIntensity", &ambientLightIntensity, 0.f, 10.f)) { - renderer.setParam("ambientLightIntensity", ambientLightIntensity); - changed = true; - } - - if (changed) { - renderer.commit(); - } - - return changed; -} - -bool addIsosurfacesUI(GLFWVKLWindow &window, std::vector &isoValues) -{ - auto &renderer = window.currentRenderer(); - - static bool showIsosurfaces = true; - - static constexpr int maxNumIsosurfaces = 3; - - struct IsosurfaceParameters - { - bool enabled{true}; - float isovalue{0.f}; - }; - - static std::array isosurfaces; - - static bool initialized = false; - bool isosurfacesChanged = false; - - if (!initialized) { - isosurfaces[0].isovalue = -1.f; - isosurfaces[1].isovalue = 0.f; - isosurfaces[2].isovalue = 1.f; - - initialized = true; - isosurfacesChanged = true; // Update isovalues on init! - } - - if (ImGui::Checkbox("show isosurfaces", &showIsosurfaces)) { - isosurfacesChanged = true; - } - - if (showIsosurfaces) { - int labelCounter = 0; - - for (auto &isosurface : isosurfaces) { - std::ostringstream enabledLabel; - enabledLabel << "##enabled_isosurface " << labelCounter; - - std::ostringstream isovalueLabel; - isovalueLabel << "isosurface " << labelCounter; - - if (ImGui::Checkbox(enabledLabel.str().c_str(), &isosurface.enabled)) { - isosurfacesChanged = true; - } - - ImGui::SameLine(); - - if (ImGui::SliderFloat( - isovalueLabel.str().c_str(), &isosurface.isovalue, -1.f, 1.f)) { - isosurfacesChanged = true; - } - - labelCounter++; - } - } - - if (isosurfacesChanged) { - isoValues.clear(); - - if (showIsosurfaces) { - for (const auto &isosurface : isosurfaces) { - if (isosurface.enabled) { - isoValues.push_back(isosurface.isovalue); - } - } - } - } - - if (isosurfacesChanged) { - renderer.commit(); - } - - return isosurfacesChanged; -} - -bool addIntervalIteratorDebugUI(GLFWVKLWindow &window) -{ - auto &renderer = window.currentRenderer(); - - bool changed = false; - - static int intervalColorScale = 4; - if (ImGui::SliderInt("intervalColorScale", &intervalColorScale, 1, 32)) { - renderer.setParam("intervalColorScale", float(intervalColorScale)); - changed = true; - } - - static float intervalOpacity = 0.25f; - if (ImGui::SliderFloat("intervalOpacity", &intervalOpacity, 0.01f, 1.f)) { - renderer.setParam("intervalOpacity", intervalOpacity); - changed = true; - } - - static bool firstIntervalOnly = false; - if (ImGui::Checkbox("firstIntervalOnly", &firstIntervalOnly)) { - renderer.setParam("firstIntervalOnly", firstIntervalOnly); - changed = true; - } - - static bool showIntervalBorders = false; - if (ImGui::Checkbox("showIntervalBorders", &showIntervalBorders)) { - renderer.setParam("showIntervalBorders", showIntervalBorders); - changed = true; - } - - if (changed) { - renderer.commit(); - } - - return changed; -} - -void usage(const char *progname) -{ - std::cerr - << "usage: " << progname << "\n" - << "\t-renderer density_pathtracer | hit_iterator |" - " ray_march_iterator | interval_iterator_debug\n" - "\t-gridType structuredRegular | structuredSpherical | " - "unstructured | amr | vdb | particle\n" - "\t-gridOrigin \n" - "\t-gridSpacing \n" - "\t-gridDimensions \n" - "\t-voxelType uchar | short | ushort | half | float | double\n" - "\t-valueRange \n" - "\t-multiAttribute (vdb and structuredRegular only, ignores -field)\n" - "\t-motionBlur structured | unstructured (structuredRegular and vdb)\n" - "\t-filter nearest | trilinear (structured and vdb) | tricubic " - "(structured and vdb)\n" - "\t-field wavelet | xyz | sphere | torus (vdb float only) | \n" - "\t-file \n" - "\t-numParticles (particle only)\n" - "\t-disable-vsync\n" - "\t-ispc\n" - "\t-windowSize \n" - "\t-pixelRange \n" - "\t-o \n" - "\t-innerNodeOutput (vdb only)\n" - "\t-innerNodeMaxDepth (vdb only)\n" - "\t-background | undefined\n" - << std::endl; -} - -bool parseCommandLine(int argc, const char **argv, ViewerParams ¶ms) -{ - int argIndex = 1; - while (argIndex < argc) { - std::string switchArg(argv[argIndex++]); - - if (switchArg == "-gridType") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -gridType arguments"); - } - - params.gridType = std::string(argv[argIndex++]); - } else if (switchArg == "-o") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -o arguments"); - } - params.interactive = false; - params.fileNameOut = std::string(argv[argIndex++]); - } else if (switchArg == "-ispc") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -ispc arguments"); - } - params.useISPC = atoi(argv[argIndex++]); - } else if (switchArg == "-windowSize") { - if (argc < argIndex + 2) { - throw std::runtime_error("improper -windowSize arguments"); - } - params.windowSize.x = atoi(argv[argIndex++]); - params.windowSize.y = atoi(argv[argIndex++]); - } else if (switchArg == "-pixelRange") { - if (argc < argIndex + 4) { - throw std::runtime_error("improper -pixelRange arguments"); - } - params.cmdlinePixelRange = true; - params.pixelRange.lower.x = atoi(argv[argIndex++]); - int y0 = atoi(argv[argIndex++]); - params.pixelRange.upper.x = atoi(argv[argIndex++]); - int y1 = atoi(argv[argIndex++]); - params.pixelRange.lower.y = params.windowSize.y - y1; - params.pixelRange.upper.y = params.windowSize.y - y0; - } else if (switchArg == "-disable-vsync") { - params.disableVSync = true; - } else if (switchArg == "-gridOrigin") { - if (argc < argIndex + 3) { - throw std::runtime_error("improper -gridOrigin arguments"); - } - - const std::string gridOriginX(argv[argIndex++]); - const std::string gridOriginY(argv[argIndex++]); - const std::string gridOriginZ(argv[argIndex++]); - - params.gridOrigin = - vec3f(stof(gridOriginX), stof(gridOriginY), stof(gridOriginZ)); - } else if (switchArg == "-gridSpacing") { - if (argc < argIndex + 3) { - throw std::runtime_error("improper -gridSpacing arguments"); - } - - const std::string gridSpacingX(argv[argIndex++]); - const std::string gridSpacingY(argv[argIndex++]); - const std::string gridSpacingZ(argv[argIndex++]); - - params.gridSpacing = - vec3f(stof(gridSpacingX), stof(gridSpacingY), stof(gridSpacingZ)); - } else if (switchArg == "-gridDimensions") { - if (argc < argIndex + 3) { - throw std::runtime_error("improper -gridDimensions arguments"); - } - - const std::string dimX(argv[argIndex++]); - const std::string dimY(argv[argIndex++]); - const std::string dimZ(argv[argIndex++]); - - params.dimensions = vec3i(stoi(dimX), stoi(dimY), stoi(dimZ)); - } else if (switchArg == "-valueRange") { - if (argc < argIndex + 2) { - throw std::runtime_error("improper -valueRange arguments"); - } - - const std::string rangeLower(argv[argIndex++]); - const std::string rangeUpper(argv[argIndex++]); - - params.initialValueRange = range1f(stof(rangeLower), stof(rangeUpper)); - } else if (switchArg == "-voxelType") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -voxelType argument"); - } - - std::map stringToVKLDataType = { - {"uchar", VKL_UCHAR}, - {"short", VKL_SHORT}, - {"ushort", VKL_USHORT}, - {"half", VKL_HALF}, - {"float", VKL_FLOAT}, - {"double", VKL_DOUBLE}}; - - params.voxelTypeString = std::string(argv[argIndex++]); - - if (!stringToVKLDataType.count(params.voxelTypeString)) { - throw std::runtime_error("unsupported -voxelType specified"); - } - - params.voxelType = stringToVKLDataType[params.voxelTypeString]; - } else if (switchArg == "-multiAttribute") { - params.multiAttribute = true; - } else if (switchArg == "-motionBlur") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -motionBlur arguments"); - } - - std::string motionBlurType = argv[argIndex++]; - - if (motionBlurType == "structured") { - params.motionBlurStructured = true; - } else if (motionBlurType == "unstructured") { - params.motionBlurUnstructured = true; - } else { - throw std::runtime_error("improper -motionBlur arguments"); - } - } else if (switchArg == "-file") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -file arguments"); - } - - params.filename = argv[argIndex++]; - } else if (switchArg == "-field") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -field arguments"); - } - - params.field = argv[argIndex++]; - } else if (switchArg == "-filter") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -filter arguments"); - } - - params.haveFilter = true; - const std::string filterArg = argv[argIndex++]; - if (filterArg == "trilinear") - params.filter = VKL_FILTER_TRILINEAR; - else if (filterArg == "tricubic") - params.filter = VKL_FILTER_TRICUBIC; - else if (filterArg == "nearest") - params.filter = VKL_FILTER_NEAREST; - else - throw std::runtime_error("unsupported -filter specified"); - params.gradientFilter = params.filter; - } else if (switchArg == "-numParticles") { - params.numParticles = std::stoul(argv[argIndex++]); - } else if (switchArg == "-renderer") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -renderer arguments"); - } - - params.rendererType = argv[argIndex++]; - } else if (switchArg == "-innerNodeOutput") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -innerNodeOutput arguments"); - } - params.innerNodeOutput = argv[argIndex++]; - } else if (switchArg == "-innerNodeMaxDepth") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -innerNodeMaxDepth arguments"); - } - params.innerNodeMaxDepth = stoi(std::string(argv[argIndex++])); - } else if (switchArg == "-background") { - if (argc < argIndex + 1) { - throw std::runtime_error("improper -background arguments"); - } - const std::string bgArg = argv[argIndex++]; - if (bgArg == "undefined") { - params.background = VKL_BACKGROUND_UNDEFINED; - } else { - params.background = std::stof(bgArg); - } - } else if (switchArg == "-help") { - usage(argv[0]); - return false; - } else { - std::cerr << "unknown argument " << switchArg << std::endl; - usage(argv[0]); - throw std::runtime_error("unknown switch argument"); - } - } - - if (params.cmdlinePixelRange && - (area(params.pixelRange) <= 0 || params.pixelRange.lower.x < 0 || - params.pixelRange.lower.y < 0 || - params.pixelRange.upper.x > params.windowSize.x || - params.pixelRange.upper.y > params.windowSize.y)) { - throw std::runtime_error("invalid pixel range"); - } + Scene scene; - if (params.field.empty()) { - if (params.filename.empty()) - params.field = "wavelet"; - else - params.field = "density"; - } + std::list args(argv, argv + argc); - if (params.haveFilter && params.gridType != "vdb" && - params.gridType != "structuredRegular" && - params.gridType != "structuredSpherical") { - std::cerr << "warning: -filter has no effect on " << params.gridType - << " volumes" << std::endl; + if (!scene.parseCommandLine(args)) { + return 0; } - // generate params.gridOrigin and params.gridSpacing if not specified on the - // command-line - if (std::isnan(params.gridOrigin.x) || std::isnan(params.gridSpacing.x)) { - const float boundingBoxSize = 2.f; - - if (params.gridType == "structuredSpherical") { - ProceduralStructuredSphericalVolume<>::generateGridParameters( - params.dimensions, - boundingBoxSize, - params.gridOrigin, - params.gridSpacing); - } else { - // all other grid types can use values generated for structured regular - // volumes - ProceduralStructuredRegularVolume<>::generateGridParameters( - params.dimensions, - boundingBoxSize, - params.gridOrigin, - params.gridSpacing); - } - } - - return true; -} - -void setupVolume(ViewerParams ¶ms, - std::shared_ptr &testingVolume) -{ - if (!params.filename.empty()) { - std::cout << "filename: " << params.filename << std::endl; + initializeOpenVKL(); - std::string ext = params.filename.substr(params.filename.size() - 4); - std::for_each(ext.begin(), ext.end(), [](char &c) { c = ::tolower(c); }); - if (ext == ".vdb") { - params.gridType = "vdb"; - // avoid deferred loading when exporting innerNodes to ensure exported - // value ranges represent the full data - auto vol = std::shared_ptr( - OpenVdbVolume::loadVdbFile(getOpenVKLDevice(), - params.filename, - params.field, - params.filter, - params.innerNodeOutput.empty())); - testingVolume = std::move(vol); - params.haveVdb = true; - } else if (ext == ".rwh") { - testingVolume = std::shared_ptr( - new RawHFileStructuredVolume(params.filename, - params.gridType, - params.gridOrigin, - params.gridSpacing)); + try { + if (scene.interactive) { + InteractiveApplication app; + app.run(scene); } else { - testingVolume = std::shared_ptr( - new RawFileStructuredVolume(params.filename, - params.gridType, - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.voxelType)); + BatchApplication app; + app.run(scene); } - } else { - if (params.gridType == "structuredRegular") { - TemporalConfig temporalConfig; - - if (params.motionBlurStructured) { - temporalConfig = - TemporalConfig(TemporalConfig::Structured, - params.motionBlurStructuredNumTimesteps); - } else if (params.motionBlurUnstructured) { - if (params.field == "sphere" && !params.multiAttribute) { - temporalConfig = TemporalConfig(TemporalConfig::Unstructured, 256); - temporalConfig.useTemporalCompression = true; - temporalConfig.temporalCompressionThreshold = 0.05f; - } else { - temporalConfig = - TemporalConfig(params.motionBlurUnstructuredTimeSamples); - } - } - - if (params.multiAttribute) { - testingVolume = std::shared_ptr( - generateMultiAttributeStructuredRegularVolume( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig, - VKL_DATA_SHARED_BUFFER, - false)); - } else { - if (params.voxelType == VKL_UCHAR) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else if (params.voxelType == VKL_SHORT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else if (params.voxelType == VKL_USHORT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else if (params.voxelType == VKL_HALF) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else if (params.voxelType == VKL_FLOAT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else if (params.voxelType == VKL_DOUBLE) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } else { - testingVolume = - std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - temporalConfig); - } - } else { - throw std::runtime_error( - "cannot create procedural structuredRegular volume for unknown " - "voxel type"); - } - } - } - - else if (params.gridType == "structuredSpherical") { - if (params.voxelType == VKL_UCHAR) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else if (params.voxelType == VKL_SHORT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else if (params.voxelType == VKL_USHORT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else if (params.voxelType == VKL_HALF) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else if (params.voxelType == VKL_FLOAT) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else if (params.voxelType == VKL_DOUBLE) { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } else { - testingVolume = - std::make_shared( - params.dimensions, params.gridOrigin, params.gridSpacing); - } - } else { - throw std::runtime_error( - "cannot create procedural structuredSpherical volume for unknown " - "voxel type"); - } - } - - else if (params.gridType == "unstructured") { - if (params.field == "xyz") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - VKL_HEXAHEDRON, - false); - } else if (params.field == "sphere") { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - VKL_HEXAHEDRON, - false); - } else if (params.field == "mixed") { - testingVolume = std::make_shared(); - } else { - testingVolume = std::make_shared( - params.dimensions, - params.gridOrigin, - params.gridSpacing, - VKL_HEXAHEDRON, - false); - } - } - - else if (params.gridType == "amr") { - if (params.voxelType == VKL_FLOAT) { - testingVolume = std::shared_ptr>( - new ProceduralShellsAMRVolume<>( - params.dimensions, params.gridOrigin, params.gridSpacing)); - } else { - throw std::runtime_error( - "cannot create procedural AMR volume for non-float voxel type"); - } - } - - else if (params.gridType == "vdb") { - TemporalConfig temporalConfig; - if (params.motionBlurStructured) { - temporalConfig = - TemporalConfig(TemporalConfig::Structured, - params.motionBlurStructuredNumTimesteps); - } else if (params.motionBlurUnstructured) { - if (params.field == "sphere" && !params.multiAttribute) { - temporalConfig = TemporalConfig(TemporalConfig::Unstructured, 256); - temporalConfig.useTemporalCompression = true; - temporalConfig.temporalCompressionThreshold = 0.05f; - } else { - temporalConfig = - TemporalConfig(params.motionBlurUnstructuredTimeSamples); - } - } - - if (!temporalConfig.hasTime() && params.multiAttribute) { - if (params.voxelType == VKL_HALF) { - testingVolume = std::shared_ptr( - generateMultiAttributeVdbVolumeHalf(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - VKL_DATA_SHARED_BUFFER, - false)); - } else if (params.voxelType == VKL_FLOAT) { - testingVolume = std::shared_ptr( - generateMultiAttributeVdbVolumeFloat(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - VKL_DATA_SHARED_BUFFER, - false)); - } else { - throw std::runtime_error( - "can only create procedural VDB multi-attribute volumes for " - "VKL_HALF or VKL_FLOAT voxel types"); - } - } else { - const uint32_t numAttributes = params.multiAttribute ? 3 : 1; - if (params.voxelType == VKL_HALF) { - if (params.field == "xyz") { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } else { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } - } else if (params.voxelType == VKL_FLOAT) { - if (params.field == "xyz") { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } else if (params.field == "sphere") { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } else if (params.field == "torus") { - testingVolume = std::make_shared(); - } else { - testingVolume = - std::make_shared(getOpenVKLDevice(), - params.dimensions, - params.gridOrigin, - params.gridSpacing, - params.filter, - temporalConfig, - numAttributes); - } - } else { - throw std::runtime_error( - "can only create procedural VDB volumes for VKL_HALF or " - "VKL_FLOAT voxel types"); - } - } - - params.haveVdb = true; - } - - else if (params.gridType == "particle") { - testingVolume = - std::make_shared(params.numParticles); - } - - else { - throw std::runtime_error("unknown gridType specified"); - } - } - - if (params.haveFilter && !params.haveVdb && - params.gridType != "structuredRegular") { - std::cerr << "warning: -filter has no effect on " << params.gridType - << " volumes" << std::endl; - } - - VKLVolume vklVolume = testingVolume->getVKLVolume(getOpenVKLDevice()); - - vklSetFloat(vklVolume, "background", params.background); - vklCommit(vklVolume); -} - -void setupSampler(const ViewerParams ¶ms, Scene &scene) -{ - vklSetInt(scene.sampler, "filter", params.filter); - vklSetInt(scene.sampler, "gradientFilter", params.gradientFilter); - vklSetInt(scene.sampler, "maxSamplingDepth", params.maxSamplingDepth); - vklCommit(scene.sampler); -} - -void setupIntervalIteratorContext(const ViewerParams ¶ms, Scene &scene) -{ - vklSetFloat(scene.intervalContext, - "intervalResolutionHint", - params.intervalResolutionHint); - vklCommit(scene.intervalContext); -} - -void setupScene(const ViewerParams ¶ms, - TestingVolume *testingVolume, - Scene &scene) -{ - scene.updateVolume(testingVolume->getVKLVolume(getOpenVKLDevice())); - setupSampler(params, scene); - setupIntervalIteratorContext(params, scene); -} - -void logToOutput(const ViewerParams ¶ms, const Scene &scene) -{ - std::cout << "renderer: " << params.rendererType << std::endl; - std::cout << "gridType: " << params.gridType << std::endl; - std::cout << "gridDimensions: " << params.dimensions << std::endl; - std::cout << "gridOrigin: " << params.gridOrigin << std::endl; - std::cout << "gridSpacing: " << params.gridSpacing << std::endl; - std::cout << "voxelType: " << params.voxelTypeString << std::endl; - std::cout << "field: " << params.field << std::endl; - - vkl_box3f bbox = vklGetBoundingBox(scene.volume); - - std::cout << "boundingBox: " - << "(" << bbox.lower.x << ", " << bbox.lower.y << ", " - << bbox.lower.z << ") -> (" << bbox.upper.x << ", " << bbox.upper.y - << ", " << bbox.upper.z << ")" << std::endl; -} - -void interactiveRender(ViewerParams ¶ms, - Scene &scene, - TestingVolume *testingVolume) -{ - VKLObserver leafAccessObserver = nullptr; - { - auto vdbVolume = dynamic_cast(testingVolume); - if (vdbVolume) - leafAccessObserver = vdbVolume->newLeafAccessObserver(scene.sampler); - } - TransferFunction transferFunction; - std::vector isoValues; - auto glfwVKLWindow = rkcommon::make_unique( - params.windowSize, scene, params.rendererType, params.disableVSync); - - if (params.cmdlinePixelRange) { - glfwVKLWindow->setRenderPixelRange(params.pixelRange); - } - - glfwVKLWindow->registerImGuiCallback([&]() { - bool changed = false; - - static int whichRenderer = 0; - if (ImGui::Combo("renderer", - &whichRenderer, - "density_pathtracer\0hit_iterator\0ray_march_" - "iterator\0interval_iterator_debug\0\0")) { - switch (whichRenderer) { - case 0: - params.rendererType = "density_pathtracer"; - break; - case 1: - params.rendererType = "hit_iterator"; - break; - case 2: - params.rendererType = "ray_march_iterator"; - break; - case 3: - params.rendererType = "interval_iterator_debug"; - break; - default: - break; - } - - glfwVKLWindow->setActiveRenderer(params.rendererType); - } - - bool samplerParamsChanged = false; - bool intervalIteratorContextParamsChanged = false; - - if (ImGui::SliderFloat("intervalResolutionHint", - ¶ms.intervalResolutionHint, - 0.f, - 1.f)) { - intervalIteratorContextParamsChanged = true; - } - - if (params.gridType == "structuredRegular" || - params.gridType == "structuredSpherical" || params.gridType == "vdb") { - static std::map filters = { - {VKL_FILTER_TRICUBIC, "tricubic"}, - {VKL_FILTER_NEAREST, "nearest"}, - {VKL_FILTER_TRILINEAR, "trilinear"}}; - - if (ImGui::BeginCombo("filter", filters[params.filter])) { - for (auto it : filters) { - if (it.first == VKL_FILTER_TRICUBIC && - (params.gridType != "vdb" && - params.gridType != "structuredRegular")) - continue; - const bool isSelected = (params.filter == it.first); - if (ImGui::Selectable(filters[it.first], isSelected)) { - params.filter = it.first; - samplerParamsChanged = true; - } - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - if (ImGui::BeginCombo("gradientFilter", filters[params.gradientFilter])) { - for (auto it : filters) { - if (it.first == VKL_FILTER_TRICUBIC && - (params.gridType != "vdb" && - params.gridType != "structuredRegular")) - continue; - const bool isSelected = (params.filter == it.first); - if (ImGui::Selectable(filters[it.first], isSelected)) { - params.gradientFilter = it.first; - samplerParamsChanged = true; - } - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - } - - // VDB specific parameters. - if (params.gridType == "vdb") { - if (ImGui::SliderInt("maxSamplingDepth", - ¶ms.maxSamplingDepth, - 0, - VKL_VDB_NUM_LEVELS - 1)) { - samplerParamsChanged = true; - } - } - - if (samplerParamsChanged) { - setupSampler(params, scene); - changed = true; - } - - if (intervalIteratorContextParamsChanged) { - setupIntervalIteratorContext(params, scene); - changed = true; - } - - static int useISPC = params.useISPC; - if (ImGui::Combo("OpenVKL API used", &useISPC, "C scalar\0ISPC\0\0")) { - glfwVKLWindow->setUseISPC(useISPC); - changed = true; - } - - static int spp = 1; - if (ImGui::SliderInt("spp", &spp, 1, 16)) { - auto &renderer = glfwVKLWindow->currentRenderer(); - renderer.setParam("spp", spp); - renderer.commit(); - } - - static int attributeIndex = 0; - unsigned int numAttributes = vklGetNumAttributes(scene.volume); - - if (numAttributes > 1) { - if (ImGui::SliderInt( - "attributeIndex", &attributeIndex, 0, numAttributes - 1)) { - scene.updateAttributeIndex(attributeIndex); - changed = true; - } - } - - if (params.motionBlurStructured || params.motionBlurUnstructured) { - static float time = 0.f; - if (ImGui::SliderFloat("time", &time, 0.f, 1.f)) { - scene.time = time; - changed = true; - } - } - - if (params.rendererType == "ray_march_iterator") { - changed |= addSamplingRateUI(*glfwVKLWindow); - } - - if (params.rendererType == "density_pathtracer") { - changed |= addPathTracerUI(*glfwVKLWindow, scene); - } - - if (params.rendererType == "hit_iterator") { - if (addIsosurfacesUI(*glfwVKLWindow, isoValues)) { - changed = true; - scene.updateHitIteratorContextValues(isoValues); - } - } - - if (params.rendererType == "interval_iterator_debug") { - changed |= addIntervalIteratorDebugUI(*glfwVKLWindow); - } - - auto transferFunctionUpdatedCallback = - [&](const range1f &valueRange, - const std::vector &colorsAndOpacities) { - transferFunction = TransferFunction{valueRange, colorsAndOpacities}; - scene.tfColorsAndOpacities = - transferFunction.colorsAndOpacities.data(); - scene.tfNumColorsAndOpacities = - transferFunction.colorsAndOpacities.size(); - scene.tfValueRange = valueRange; - scene.updateIntervalIteratorContextValueRanges(transferFunction); - glfwVKLWindow->resetAccumulation(); - }; - - static TransferFunctionWidget transferFunctionWidget( - transferFunctionUpdatedCallback, params.initialValueRange); - transferFunctionWidget.updateUI(); - - if (changed) { - glfwVKLWindow->resetAccumulation(); - } - }); - - glfwVKLWindow->registerEndOfFrameCallback([&]() { - auto vdbVolume = dynamic_cast(testingVolume); - if (vdbVolume && vdbVolume->updateVolume(leafAccessObserver)) { - scene.updateVolume(vdbVolume->getVKLVolume(getOpenVKLDevice())); - setupSampler(params, scene); - scene.updateIntervalIteratorContextValueRanges(transferFunction); - scene.updateHitIteratorContextValues(isoValues); - - if (leafAccessObserver) - vklRelease(leafAccessObserver); - leafAccessObserver = vdbVolume->newLeafAccessObserver(scene.sampler); - - glfwVKLWindow->resetAccumulation(); - } - }); - - // start the GLFW main loop, which will continuously render - glfwVKLWindow->mainLoop(); - - if (leafAccessObserver) { - vklRelease(leafAccessObserver); - } - - glfwVKLWindow.reset(); -} - -void imageWrite(ViewerParams ¶ms, - Scene &scene, - TestingVolume *testingVolume) -{ - static TransferFunction transferFunction; - - scene.tfValueRange = box1f(0.f, 1.f); - scene.tfColorsAndOpacities = transferFunction.colorsAndOpacities.data(); - scene.tfNumColorsAndOpacities = transferFunction.colorsAndOpacities.size(); - - // and default iterator contexts - scene.updateIntervalIteratorContextValueRanges(transferFunction); - scene.updateHitIteratorContextValues(std::vector{-1.f, 0.f, 1.f}); - - auto window = rkcommon::make_unique( - params.windowSize, scene, params.rendererType); - - window->setUseISPC(params.useISPC); - if (params.cmdlinePixelRange) { - window->setRenderPixelRange(params.pixelRange); - } - window->render(); - - // save image on completion of benchmark; note we apparently have no way to - // get the formal benchmark name, so we'll create one here - std::stringstream ss; - ss << params.fileNameOut; - window->savePPM(ss.str()); -} - -int main(int argc, const char **argv) -{ - ViewerParams params; - if (!parseCommandLine(argc, argv, params)) { - return 1; - } - - initializeOpenVKL(); - - std::shared_ptr testingVolume; - setupVolume(params, testingVolume); - - Scene scene; - setupScene(params, testingVolume.get(), scene); - - logToOutput(params, scene); - - if (params.gridType == "vdb" && !params.innerNodeOutput.empty()) { - openvkl::utility::vdb::exportInnerNodes( - params.innerNodeOutput, - params.innerNodeMaxDepth, - testingVolume->getVKLVolume(getOpenVKLDevice())); - } - - if (params.interactive) { - interactiveRender(params, scene, testingVolume.get()); - } else { - imageWrite(params, scene, testingVolume.get()); + } catch (const std::exception &e) { + // Handle fatal errors here. This mainly applies when invalid + // volume parameters are specified on the command line, so that no volume + // can be created at all. + std::cerr << e.what() << std::endl; } - // cleanly shut VKL down - scene = Scene(); - testingVolume.reset(); shutdownOpenVKL(); return 0; diff --git a/examples/interactive/window/ArcballCamera.cpp b/examples/interactive/window/ArcballCamera.cpp deleted file mode 100644 index c1cd0b2f..00000000 --- a/examples/interactive/window/ArcballCamera.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2019 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "ArcballCamera.h" - -ArcballCamera::ArcballCamera(const box3f &worldBounds, const vec2i &windowSize) - : zoomSpeed(1), - invWindowSize(vec2f(1.0) / vec2f(windowSize)), - centerTranslation(one), - translation(one), - rotation(one) -{ - resetCamera(worldBounds); -} - -void ArcballCamera::rotate(const vec2f &from, const vec2f &to) -{ - rotation = screenToArcball(to) * screenToArcball(from) * rotation; - updateCamera(); -} - -void ArcballCamera::zoom(float amount) -{ - amount *= zoomSpeed; - translation = AffineSpace3f::translate(vec3f(0, 0, amount)) * translation; - updateCamera(); -} - -void ArcballCamera::pan(const vec2f &delta) -{ - const vec3f t = - vec3f(-delta.x * invWindowSize.x, delta.y * invWindowSize.y, 0); - const vec3f worldt = translation.p.z * xfmVector(invCamera, t); - centerTranslation = AffineSpace3f::translate(worldt) * centerTranslation; - updateCamera(); -} - -vec3f ArcballCamera::eyePos() const -{ - return xfmPoint(invCamera, vec3f(0, 0, 1)); -} - -vec3f ArcballCamera::center() const -{ - return -centerTranslation.p; -} - -vec3f ArcballCamera::lookDir() const -{ - return xfmVector(invCamera, vec3f(0, 0, 1)); -} - -vec3f ArcballCamera::upDir() const -{ - return xfmVector(invCamera, vec3f(0, 1, 0)); -} - -void ArcballCamera::resetCamera(const box3f &worldBounds) -{ - vec3f diag = worldBounds.size(); - zoomSpeed = max(length(diag) / 150.0, 0.001); - diag = max(diag, vec3f(0.3f * length(diag))); - - centerTranslation = AffineSpace3f::translate(-worldBounds.center()); - translation = AffineSpace3f::translate(vec3f(0, 0, length(diag))); - rotation = one; - - updateCamera(); -} - -void ArcballCamera::updateCamera() -{ - const AffineSpace3f rot = LinearSpace3f(rotation); - const AffineSpace3f camera = translation * rot * centerTranslation; - invCamera = rcp(camera); -} - -void ArcballCamera::updateWindowSize(const vec2i &windowSize) -{ - invWindowSize = vec2f(1) / vec2f(windowSize); -} - -quaternionf ArcballCamera::screenToArcball(const vec2f &p) -{ - const float dist = dot(p, p); - // If we're on/in the sphere return the point on it - if (dist <= 1.f) { - return quaternionf(0, p.x, p.y, std::sqrt(1.f - dist)); - } else { - // otherwise we project the point onto the sphere - const vec2f unitDir = normalize(p); - return quaternionf(0, unitDir.x, unitDir.y, 0); - } -} diff --git a/examples/interactive/window/ArcballCamera.h b/examples/interactive/window/ArcballCamera.h deleted file mode 100644 index f2273fc6..00000000 --- a/examples/interactive/window/ArcballCamera.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019-2020 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "rkcommon/math/AffineSpace.h" - -using namespace rkcommon::math; - -class ArcballCamera -{ - public: - ArcballCamera(const box3f &worldBounds, const vec2i &windowSize); - - // All mouse positions passed should be in [-1, 1] normalized screen coords - void rotate(const vec2f &from, const vec2f &to); - void zoom(float amount); - void pan(const vec2f &delta); - - vec3f eyePos() const; - vec3f center() const; - vec3f lookDir() const; - vec3f upDir() const; - - void resetCamera(const box3f &worldBounds); - - void updateWindowSize(const vec2i &windowSize); - - protected: - void updateCamera(); - - // Project the point in [-1, 1] screen space onto the arcball sphere - quaternionf screenToArcball(const vec2f &p); - - float zoomSpeed; - vec2f invWindowSize; - AffineSpace3f centerTranslation, translation, invCamera; - quaternionf rotation; -}; diff --git a/examples/interactive/window/GLFWVKLWindow.cpp b/examples/interactive/window/GLFWVKLWindow.cpp deleted file mode 100644 index bdc09409..00000000 --- a/examples/interactive/window/GLFWVKLWindow.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "GLFWVKLWindow.h" -// std -#include -#include -// imgui -#include -#include -#include -// rkcommon -#include "rkcommon/utility/CodeTimer.h" - -namespace openvkl { - namespace examples { - - GLFWVKLWindow *GLFWVKLWindow::activeWindow = nullptr; - - GLFWVKLWindow::GLFWVKLWindow(const vec2i &windowSize, - const Scene &scene, - std::string rendererType, - bool disableVSync) - : VKLWindow(windowSize, scene, rendererType) - { - if (activeWindow != nullptr) - throw std::runtime_error("Cannot create more than one VKLWindow!"); - - activeWindow = this; - - if (!glfwInit()) - throw std::runtime_error("Failed to initialize GLFW!"); - - glfwWindow = glfwCreateWindow( - windowSize.x, windowSize.y, "VKL Example", nullptr, nullptr); - - if (!glfwWindow) { - glfwTerminate(); - throw std::runtime_error("Failed to create GLFW window!"); - } - - glfwMakeContextCurrent(glfwWindow); - if (disableVSync) - glfwSwapInterval(0); - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(glfwWindow, true); - ImGui_ImplOpenGL2_Init(); - - glEnable(GL_TEXTURE_2D); - glDisable(GL_LIGHTING); - - glGenTextures(1, &framebufferTexture); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, framebufferTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glfwSetFramebufferSizeCallback( - glfwWindow, [](GLFWwindow *, int newWidth, int newHeight) { - activeWindow->reshape(vec2i{newWidth, newHeight}); - }); - - glfwSetCursorPosCallback( - glfwWindow, [](GLFWwindow *, double x, double y) { - ImGuiIO &io = ImGui::GetIO(); - if (!io.WantCaptureMouse) - activeWindow->motion(vec2f{float(x), float(y)}); - }); - - glfwSetCharCallback(glfwWindow, [](GLFWwindow *, unsigned int c) { - ImGuiIO &io = ImGui::GetIO(); - if (c > 0 && c < 0x10000) - io.AddInputCharacter((unsigned short)c); - }); - - glfwSetKeyCallback( - glfwWindow, - [](GLFWwindow *gw, int key, int scancode, int action, int mods) { - ImGui_ImplGlfw_KeyCallback(gw, key, scancode, action, mods); - if (action == GLFW_PRESS) { - switch (key) { - case GLFW_KEY_G: - activeWindow->showUi = !(activeWindow->showUi); - break; - case GLFW_KEY_Q: - std::exit(0); - break; - } - } - }); - - // trigger window reshape events with current window size - glfwGetFramebufferSize( - glfwWindow, &this->windowSize.x, &this->windowSize.y); - reshape(this->windowSize); - } - - GLFWVKLWindow::~GLFWVKLWindow() - { - ImGui_ImplOpenGL2_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - glfwTerminate(); - } - - GLFWVKLWindow *GLFWVKLWindow::getActiveWindow() - { - return activeWindow; - } - - void GLFWVKLWindow::setWindowTitle(const std::string &newWindowTitle) - { - windowTitle = newWindowTitle; - } - - void GLFWVKLWindow::registerImGuiCallback(std::function callback) - { - uiCallback = callback; - } - - void GLFWVKLWindow::registerEndOfFrameCallback( - std::function callback) - { - endOfFrameCallback = callback; - } - - void GLFWVKLWindow::mainLoop() - { - while (!glfwWindowShouldClose(glfwWindow)) { - ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - display(); - - glfwPollEvents(); - - if (endOfFrameCallback) - endOfFrameCallback(); - } - } - - void GLFWVKLWindow::reshape(const vec2i &newWindowSize) - { - VKLWindow::reshape(newWindowSize); - - glViewport(0, 0, windowSize.x, windowSize.y); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0, windowSize.x, 0.0, windowSize.y, -1.0, 1.0); - } - - void GLFWVKLWindow::motion(const vec2f &position) - { - static vec2f previousMouse(-1); - - const vec2f mouse(position.x, position.y); - if (previousMouse != vec2f(-1)) { - const bool leftDown = - glfwGetMouseButton(glfwWindow, GLFW_MOUSE_BUTTON_LEFT) == - GLFW_PRESS; - const bool rightDown = - glfwGetMouseButton(glfwWindow, GLFW_MOUSE_BUTTON_RIGHT) == - GLFW_PRESS; - const bool middleDown = - glfwGetMouseButton(glfwWindow, GLFW_MOUSE_BUTTON_MIDDLE) == - GLFW_PRESS; - const vec2f prev = previousMouse; - - bool cameraChanged = leftDown || rightDown || middleDown; - - if (leftDown) { - const vec2f mouseFrom( - clamp(prev.x * 2.f / windowSize.x - 1.f, -1.f, 1.f), - clamp(prev.y * 2.f / windowSize.y - 1.f, -1.f, 1.f)); - const vec2f mouseTo( - clamp(mouse.x * 2.f / windowSize.x - 1.f, -1.f, 1.f), - clamp(mouse.y * 2.f / windowSize.y - 1.f, -1.f, 1.f)); - arcballCamera->rotate(mouseFrom, mouseTo); - } else if (rightDown) { - arcballCamera->zoom(mouse.y - prev.y); - } else if (middleDown) { - arcballCamera->pan(vec2f(mouse.x - prev.x, prev.y - mouse.y)); - } - - if (cameraChanged) { - updateCamera(); - } - } - - previousMouse = mouse; - } - - void GLFWVKLWindow::display() - { - static rkcommon::utility::CodeTimer displayTimer; - static rkcommon::utility::CodeTimer renderTimer; - - displayTimer.start(); - - if (showUi && uiCallback) { - ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize; - ImGui::Begin( - "Tutorial Controls (press 'g' to hide / show)", nullptr, flags); - uiCallback(); - ImGui::End(); - } - - renderTimer.start(); - render(); - renderTimer.stop(); - - const auto &fb = renderer->frameBuffer(); - - glBindTexture(GL_TEXTURE_2D, framebufferTexture); - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGB, - windowSize.x, - windowSize.y, - 0, - GL_RGB, - GL_FLOAT, - fb.data()); - - glClear(GL_COLOR_BUFFER_BIT); - - glBegin(GL_QUADS); - - glTexCoord2f(0.f, 0.f); - glVertex2f(0.f, 0.f); - - glTexCoord2f(0.f, 1.f); - glVertex2f(0.f, windowSize.y); - - glTexCoord2f(1.f, 1.f); - glVertex2f(windowSize.x, windowSize.y); - - glTexCoord2f(1.f, 0.f); - glVertex2f(windowSize.x, 0.f); - - glEnd(); - - if (showUi && uiCallback) { - ImGui::Render(); - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - } - - glfwSwapBuffers(glfwWindow); - - displayTimer.stop(); - - std::stringstream displayWindowTitle; - displayWindowTitle << windowTitle << ": " << std::setprecision(3) - << displayTimer.perSecond() << " fps | " - << std::setprecision(3) << renderTimer.perSecond() - << " vkl"; - - glfwSetWindowTitle(glfwWindow, displayWindowTitle.str().c_str()); - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/window/GLFWVKLWindow.h b/examples/interactive/window/GLFWVKLWindow.h deleted file mode 100644 index f7b9a7ab..00000000 --- a/examples/interactive/window/GLFWVKLWindow.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2019-2020 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "VKLWindow.h" -// glfw -#include -// std -#include - -namespace openvkl { - namespace examples { - - using namespace rkcommon::math; - - class GLFWVKLWindow : public VKLWindow - { - public: - GLFWVKLWindow(const vec2i &windowSize, - const Scene &scene, - std::string rendererType, - bool disableVSync); - - ~GLFWVKLWindow() override; - - static GLFWVKLWindow *getActiveWindow(); - - void setWindowTitle(const std::string &newWindowTitle); - - void registerImGuiCallback(std::function callback); - void registerEndOfFrameCallback(std::function callback); - - void mainLoop(); - - protected: - void reshape(const vec2i &newWindowSize) override; - void motion(const vec2f &position); - void display(); - - static GLFWVKLWindow *activeWindow; - - GLFWwindow *glfwWindow = nullptr; - - GLuint framebufferTexture = 0; - - // window title base string (may be added to) - std::string windowTitle = "OpenVKL"; - - bool showUi{true}; - - // optional registered ImGui callback, called during every frame to build - // UI - std::function uiCallback; - - // optional callback after a frame was rendered - std::function endOfFrameCallback; - }; - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/window/TransferFunctionWidget.cpp b/examples/interactive/window/TransferFunctionWidget.cpp deleted file mode 100644 index 03e52c85..00000000 --- a/examples/interactive/window/TransferFunctionWidget.cpp +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "TransferFunctionWidget.h" -#include -#include -#include -#include -#include -#include - -namespace help { - - template - int find_idx(const std::vector &A, float p) - { - auto found = std::upper_bound( - A.begin(), A.end(), T(p), [](const T &a, const T &b) { return a.x < b.x; }); - return std::distance(A.begin(), found); - } - - float lerp(const float &l, - const float &r, - const float &pl, - const float &pr, - const float &p) - { - const float dl = std::abs(pr - pl) > 0.0001f ? (p - pl) / (pr - pl) : 0.f; - const float dr = 1.f - dl; - return l * dr + r * dl; - } - -} // namespace help - -TransferFunctionWidget::TransferFunctionWidget( - std::function &)> - _transferFunctionUpdatedCallback, - const range1f &_valueRange, - const std::string &_widgetName) - : transferFunctionUpdatedCallback(_transferFunctionUpdatedCallback), - valueRange(_valueRange), - widgetName(_widgetName) -{ - loadDefaultMaps(); - - tfnColorPoints = &(tfnsColorPoints[currentMap]); - tfnOpacityPoints = &(tfnsOpacityPoints[currentMap]); - tfnEditable = tfnsEditable[currentMap]; - - transferFunctionUpdatedCallback(getValueRange(), - getSampledColorsAndOpacities()); - - // set ImGui double click time to 1s, so it also works for slower frame rates - ImGuiIO &io = ImGui::GetIO(); - io.MouseDoubleClickTime = 1.f; -} - -TransferFunctionWidget::~TransferFunctionWidget() -{ - if (tfnPaletteTexture) { - glDeleteTextures(1, &tfnPaletteTexture); - } -} - -void TransferFunctionWidget::updateUI() -{ - if (tfnChanged) { - updateTfnPaletteTexture(); - transferFunctionUpdatedCallback(getValueRange(), - getSampledColorsAndOpacities()); - tfnChanged = false; - } - - // need a unique ImGui group name per widget - if (!ImGui::Begin(widgetName.c_str())) { - ImGui::End(); - return; - } - - ImGui::Text("Linear Transfer Function"); - - ImGui::Separator(); - std::vector names(tfnsNames.size(), nullptr); - std::transform(tfnsNames.begin(), - tfnsNames.end(), - names.begin(), - [](const std::string &t) { return t.c_str(); }); - - int newMap = currentMap; - if (ImGui::ListBox("Color maps", &newMap, names.data(), names.size())) { - setMap(newMap); - } - - ImGui::Separator(); - - ImGui::Text("Opacity scale"); - ImGui::SameLine(); - if (ImGui::SliderFloat("##OpacityScale", &globalOpacityScale, 0.f, 10.f)) { - tfnChanged = true; - } - - ImGui::Separator(); - - if (ImGui::DragFloatRange2("Value range", - &valueRange.lower, - &valueRange.upper, - 0.1f, - -10000.f, - 10000.0f, - "Min: %.7f", - "Max: %.7f")) { - tfnChanged = true; - } - - drawEditor(); - - ImGui::End(); -} - -range1f TransferFunctionWidget::getValueRange() -{ - return valueRange; -} - -std::vector TransferFunctionWidget::getSampledColorsAndOpacities( - int numSamples) -{ - std::vector sampledColorsAndOpacities; - - const float dx = 1.f / (numSamples - 1); - - for (int i = 0; i < numSamples; i++) { - sampledColorsAndOpacities.push_back(vec4f( - interpolateColor(*tfnColorPoints, i * dx), - interpolateOpacity(*tfnOpacityPoints, i * dx) * globalOpacityScale)); - } - - return sampledColorsAndOpacities; -} - -void TransferFunctionWidget::loadDefaultMaps() -{ - // same opacities for all maps - std::vector opacities; - - opacities.emplace_back(0.f, 0.f); - opacities.emplace_back(1.f, 1.f); - - // Jet - std::vector colors; - - colors.emplace_back(0.0f, 0.f, 0.f, 1.f); - colors.emplace_back(0.3f, 0.f, 1.f, 1.f); - colors.emplace_back(0.6f, 1.f, 1.f, 0.f); - colors.emplace_back(1.0f, 1.f, 0.f, 0.f); - - tfnsColorPoints.push_back(colors); - tfnsOpacityPoints.push_back(opacities); - - tfnsEditable.push_back(true); - tfnsNames.push_back("Jet"); - - // Ice Fire - colors.clear(); - - const float spacing = 1.f / 16; - - colors.emplace_back(0 * spacing, 0, 0, 0); - colors.emplace_back(1 * spacing, 0, 0.120394, 0.302678); - colors.emplace_back(2 * spacing, 0, 0.216587, 0.524575); - colors.emplace_back(3 * spacing, 0.0552529, 0.345022, 0.659495); - colors.emplace_back(4 * spacing, 0.128054, 0.492592, 0.720287); - colors.emplace_back(5 * spacing, 0.188952, 0.641306, 0.792096); - colors.emplace_back(6 * spacing, 0.327672, 0.784939, 0.873426); - colors.emplace_back(7 * spacing, 0.60824, 0.892164, 0.935546); - colors.emplace_back(8 * spacing, 0.881376, 0.912184, 0.818097); - colors.emplace_back(9 * spacing, 0.9514, 0.835615, 0.449271); - colors.emplace_back(10 * spacing, 0.904479, 0.690486, 0); - colors.emplace_back(11 * spacing, 0.854063, 0.510857, 0); - colors.emplace_back(12 * spacing, 0.777096, 0.330175, 0.000885023); - colors.emplace_back(13 * spacing, 0.672862, 0.139086, 0.00270085); - colors.emplace_back(14 * spacing, 0.508812, 0, 0); - colors.emplace_back(15 * spacing, 0.299413, 0.000366217, 0.000549325); - - colors.emplace_back(1.f, 0.0157473, 0.00332647, 0); - - tfnsColorPoints.push_back(colors); - tfnsOpacityPoints.push_back(opacities); - - tfnsEditable.push_back(true); - tfnsNames.push_back("Ice Fire"); - - // Grayscale - colors.clear(); - - colors.emplace_back(0.f, 1.f, 1.f, 1.f); - colors.emplace_back(1.f, 1.f, 1.f, 1.f); - - tfnsColorPoints.push_back(colors); - tfnsOpacityPoints.push_back(opacities); - - tfnsEditable.push_back(true); - tfnsNames.push_back("Grayscale"); -}; - -void TransferFunctionWidget::setMap(int selection) -{ - if (currentMap != selection) { - currentMap = selection; - // Remember to update other constructors as well - tfnColorPoints = &(tfnsColorPoints[selection]); -#if 1 // NOTE(jda) - this will use the first tf's opacities for all color maps - tfnOpacityPoints = &(tfnsOpacityPoints[selection]); -#endif - tfnEditable = tfnsEditable[selection]; - tfnChanged = true; - } -} - -vec3f TransferFunctionWidget::interpolateColor( - const std::vector &controlPoints, float x) -{ - auto first = controlPoints.front(); - if (x <= first.x) - return vec3f(first.y, first.z, first.w); - - for (uint32_t i = 1; i < controlPoints.size(); i++) { - auto current = controlPoints[i]; - auto previous = controlPoints[i - 1]; - if (x <= current.x) { - const float t = (x - previous.x) / (current.x - previous.x); - return (1.0 - t) * vec3f(previous.y, previous.z, previous.w) + - t * vec3f(current.y, current.z, current.w); - } - } - - auto last = controlPoints.back(); - return vec3f(last.x, last.y, last.z); -} - -float TransferFunctionWidget::interpolateOpacity( - const std::vector &controlPoints, float x) - -{ - auto first = controlPoints.front(); - if (x <= first.x) - return first.y; - - for (uint32_t i = 1; i < controlPoints.size(); i++) { - auto current = controlPoints[i]; - auto previous = controlPoints[i - 1]; - if (x <= current.x) { - const float t = (x - previous.x) / (current.x - previous.x); - return (1.0 - t) * previous.y + t * current.y; - } - } - - auto last = controlPoints.back(); - return last.y; -} - -void TransferFunctionWidget::updateTfnPaletteTexture() -{ - const size_t textureWidth = 256, textureHeight = 1; - - // backup currently bound texture - GLint prevBinding = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevBinding); - - // create transfer function palette texture if it doesn't exist - if (!tfnPaletteTexture) { - glGenTextures(1, &tfnPaletteTexture); - glBindTexture(GL_TEXTURE_2D, tfnPaletteTexture); - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGBA8, - textureWidth, - textureHeight, - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - } - - // sample the palette then upload the data - std::vector palette = getSampledColorsAndOpacities(textureWidth); - - // save palette to texture - glBindTexture(GL_TEXTURE_2D, tfnPaletteTexture); - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGB, - textureWidth, - textureHeight, - 0, - GL_RGBA, - GL_FLOAT, - static_cast(palette.data())); - - // restore previously bound texture - if (prevBinding) { - glBindTexture(GL_TEXTURE_2D, prevBinding); - } -} - -void TransferFunctionWidget::drawEditor() -{ - // only God and me know what do they do ... - ImDrawList *draw_list = ImGui::GetWindowDrawList(); - float canvas_x = ImGui::GetCursorScreenPos().x; - float canvas_y = ImGui::GetCursorScreenPos().y; - float canvas_avail_x = ImGui::GetContentRegionAvail().x; - float canvas_avail_y = ImGui::GetContentRegionAvail().y; - const float mouse_x = ImGui::GetMousePos().x; - const float mouse_y = ImGui::GetMousePos().y; - const float scroll_x = ImGui::GetScrollX(); - const float scroll_y = ImGui::GetScrollY(); - const float margin = 10.f; - const float width = canvas_avail_x - 2.f * margin; - const float height = 260.f; - const float color_len = 9.f; - const float opacity_len = 7.f; - - // draw preview texture - ImGui::SetCursorScreenPos(ImVec2(canvas_x + margin, canvas_y)); - ImGui::Image(reinterpret_cast(tfnPaletteTexture), - ImVec2(width, height)); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Double left click to add new control point"); - - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); - for (int i = 0; i < tfnOpacityPoints->size() - 1; ++i) { - std::vector polyline; - polyline.emplace_back(canvas_x + margin + (*tfnOpacityPoints)[i].x * width, - canvas_y + height); - polyline.emplace_back( - canvas_x + margin + (*tfnOpacityPoints)[i].x * width, - canvas_y + height - (*tfnOpacityPoints)[i].y * height); - polyline.emplace_back( - canvas_x + margin + (*tfnOpacityPoints)[i + 1].x * width + 1, - canvas_y + height - (*tfnOpacityPoints)[i + 1].y * height); - polyline.emplace_back( - canvas_x + margin + (*tfnOpacityPoints)[i + 1].x * width + 1, - canvas_y + height); - draw_list->AddConvexPolyFilled( - polyline.data(), polyline.size(), 0xFFD8D8D8); - } - canvas_y += height + margin; - canvas_avail_y -= height + margin; - - // draw color control points - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); - - if (tfnEditable) { - // draw circle background - draw_list->AddRectFilled( - ImVec2(canvas_x + margin, canvas_y - margin), - ImVec2(canvas_x + margin + width, canvas_y - margin + 2.5 * color_len), - 0xFF474646); - - // draw circles - for (int i = tfnColorPoints->size() - 1; i >= 0; --i) { - const ImVec2 pos(canvas_x + width * (*tfnColorPoints)[i].x + margin, - canvas_y); - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); - - // white background - draw_list->AddTriangleFilled(ImVec2(pos.x - 0.5f * color_len, pos.y), - ImVec2(pos.x + 0.5f * color_len, pos.y), - ImVec2(pos.x, pos.y - color_len), - 0xFFD8D8D8); - draw_list->AddCircleFilled( - ImVec2(pos.x, pos.y + 0.5f * color_len), color_len, 0xFFD8D8D8); - - // draw picker - ImVec4 picked_color = ImColor((*tfnColorPoints)[i].y, - (*tfnColorPoints)[i].z, - (*tfnColorPoints)[i].w, - 1.f); - ImGui::SetCursorScreenPos( - ImVec2(pos.x - color_len, pos.y + 1.5f * color_len)); - if (ImGui::ColorEdit4(("##ColorPicker" + std::to_string(i)).c_str(), - (float *)&picked_color, - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_NoInputs | - ImGuiColorEditFlags_NoLabel | - ImGuiColorEditFlags_AlphaPreview | - ImGuiColorEditFlags_NoOptions | - ImGuiColorEditFlags_NoTooltip)) { - (*tfnColorPoints)[i].y = picked_color.x; - (*tfnColorPoints)[i].z = picked_color.y; - (*tfnColorPoints)[i].w = picked_color.z; - tfnChanged = true; - } - if (ImGui::IsItemHovered()) { - // convert float color to char - int cr = static_cast(picked_color.x * 255); - int cg = static_cast(picked_color.y * 255); - int cb = static_cast(picked_color.z * 255); - - // setup tooltip - ImGui::BeginTooltip(); - ImVec2 sz( - ImGui::GetFontSize() * 4 + ImGui::GetStyle().FramePadding.y * 2, - ImGui::GetFontSize() * 4 + ImGui::GetStyle().FramePadding.y * 2); - ImGui::ColorButton( - "##PreviewColor", - picked_color, - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview, - sz); - ImGui::SameLine(); - ImGui::Text( - "Left click to edit\n" - "HEX: #%02X%02X%02X\n" - "RGB: [%3d,%3d,%3d]\n(%.2f, %.2f, %.2f)", - cr, - cg, - cb, - cr, - cg, - cb, - picked_color.x, - picked_color.y, - picked_color.z); - ImGui::EndTooltip(); - } - } - for (int i = 0; i < tfnColorPoints->size(); ++i) { - const ImVec2 pos(canvas_x + width * (*tfnColorPoints)[i].x + margin, - canvas_y); - - // draw button - ImGui::SetCursorScreenPos( - ImVec2(pos.x - color_len, pos.y - 0.5 * color_len)); - ImGui::InvisibleButton(("##ColorControl-" + std::to_string(i)).c_str(), - ImVec2(2.f * color_len, 2.f * color_len)); - - // dark highlight - ImGui::SetCursorScreenPos(ImVec2(pos.x - color_len, pos.y)); - draw_list->AddCircleFilled( - ImVec2(pos.x, pos.y + 0.5f * color_len), - 0.5f * color_len, - ImGui::IsItemHovered() ? 0xFF051C33 : 0xFFBCBCBC); - - // delete color point - if (ImGui::IsMouseDoubleClicked(1) && ImGui::IsItemHovered()) { - if (i > 0 && i < tfnColorPoints->size() - 1) { - tfnColorPoints->erase(tfnColorPoints->begin() + i); - tfnChanged = true; - } - } - - // drag color control point - else if (ImGui::IsItemActive()) { - ImVec2 delta = ImGui::GetIO().MouseDelta; - if (i > 0 && i < tfnColorPoints->size() - 1) { - (*tfnColorPoints)[i].x += delta.x / width; - (*tfnColorPoints)[i].x = clamp((*tfnColorPoints)[i].x, - (*tfnColorPoints)[i - 1].x, - (*tfnColorPoints)[i + 1].x); - } - - tfnChanged = true; - } - } - } - - // draw opacity control points - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); - { - // draw circles - for (int i = 0; i < tfnOpacityPoints->size(); ++i) { - const ImVec2 pos(canvas_x + width * (*tfnOpacityPoints)[i].x + margin, - canvas_y - height * (*tfnOpacityPoints)[i].y - margin); - ImGui::SetCursorScreenPos( - ImVec2(pos.x - opacity_len, pos.y - opacity_len)); - ImGui::InvisibleButton(("##OpacityControl-" + std::to_string(i)).c_str(), - ImVec2(2.f * opacity_len, 2.f * opacity_len)); - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); - - // dark bounding box - draw_list->AddCircleFilled(pos, opacity_len, 0xFF565656); - - // white background - draw_list->AddCircleFilled(pos, 0.8f * opacity_len, 0xFFD8D8D8); - - // highlight - draw_list->AddCircleFilled( - pos, - 0.6f * opacity_len, - ImGui::IsItemHovered() ? 0xFF051c33 : 0xFFD8D8D8); - - // setup interaction - - // delete opacity point - if (ImGui::IsMouseDoubleClicked(1) && ImGui::IsItemHovered()) { - if (i > 0 && i < tfnOpacityPoints->size() - 1) { - tfnOpacityPoints->erase(tfnOpacityPoints->begin() + i); - tfnChanged = true; - } - } else if (ImGui::IsItemActive()) { - ImVec2 delta = ImGui::GetIO().MouseDelta; - (*tfnOpacityPoints)[i].y -= delta.y / height; - (*tfnOpacityPoints)[i].y = clamp((*tfnOpacityPoints)[i].y, 0.0f, 1.0f); - if (i > 0 && i < tfnOpacityPoints->size() - 1) { - (*tfnOpacityPoints)[i].x += delta.x / width; - (*tfnOpacityPoints)[i].x = clamp((*tfnOpacityPoints)[i].x, - (*tfnOpacityPoints)[i - 1].x, - (*tfnOpacityPoints)[i + 1].x); - } - tfnChanged = true; - } else if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "Double right click button to delete point\n" - "Left click and drag to move point"); - } - } - } - - // draw background interaction - ImGui::SetCursorScreenPos(ImVec2(canvas_x + margin, canvas_y - margin)); - ImGui::InvisibleButton("##tfn_palette_color", ImVec2(width, 2.5 * color_len)); - - // add color point - if (tfnEditable && ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { - const float p = clamp( - (mouse_x - canvas_x - margin - scroll_x) / (float)width, 0.f, 1.f); - const int ir = help::find_idx(*tfnColorPoints, p); - const int il = ir - 1; - const float pr = (*tfnColorPoints)[ir].x; - const float pl = (*tfnColorPoints)[il].x; - const float r = - help::lerp((*tfnColorPoints)[il].y, (*tfnColorPoints)[ir].y, pl, pr, p); - const float g = - help::lerp((*tfnColorPoints)[il].z, (*tfnColorPoints)[ir].z, pl, pr, p); - const float b = - help::lerp((*tfnColorPoints)[il].w, (*tfnColorPoints)[ir].w, pl, pr, p); - ColorPoint pt(p, r, g, b); - tfnColorPoints->insert(tfnColorPoints->begin() + ir, pt); - tfnChanged = true; - } - - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Double left click to add new color point"); - } - - // draw background interaction - ImGui::SetCursorScreenPos( - ImVec2(canvas_x + margin, canvas_y - height - margin)); - ImGui::InvisibleButton("##tfn_palette_opacity", ImVec2(width, height)); - - // add opacity point - if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { - const float x = clamp( - (mouse_x - canvas_x - margin - scroll_x) / (float)width, 0.f, 1.f); - const float y = clamp( - -(mouse_y - canvas_y + margin - scroll_y) / (float)height, 0.f, 1.f); - const int idx = help::find_idx(*tfnOpacityPoints, x); - OpacityPoint pt(x, y); - tfnOpacityPoints->insert(tfnOpacityPoints->begin() + idx, pt); - tfnChanged = true; - } - - // update cursors - canvas_y += 4.f * color_len + margin; - canvas_avail_y -= 4.f * color_len + margin; - - ImGui::SetCursorScreenPos(ImVec2(canvas_x, canvas_y)); -} diff --git a/examples/interactive/window/TransferFunctionWidget.h b/examples/interactive/window/TransferFunctionWidget.h deleted file mode 100644 index 61a70290..00000000 --- a/examples/interactive/window/TransferFunctionWidget.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2019-2020 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include -#include - -#include -#ifdef _WIN32 -#undef APIENTRY -#define GLFW_EXPOSE_NATIVE_WIN32 -#define GLFW_EXPOSE_NATIVE_WGL -#include -#endif - -#include "rkcommon/math/range.h" -#include "rkcommon/math/vec.h" - -using namespace rkcommon::math; - -using ColorPoint = vec4f; -using OpacityPoint = vec2f; - -class TransferFunctionWidget -{ - public: - TransferFunctionWidget( - std::function &)> - transferFunctionUpdatedCallback, - const range1f &valueRange = range1f(-1.f, 1.f), - const std::string &widgetName = "Transfer Function"); - ~TransferFunctionWidget(); - - // update UI and process any UI events - void updateUI(); - - // getters for current transfer function data - range1f getValueRange(); - std::vector getSampledColorsAndOpacities(int numSamples = 256); - - private: - void loadDefaultMaps(); - void setMap(int); - - vec3f interpolateColor(const std::vector &controlPoints, float x); - - float interpolateOpacity(const std::vector &controlPoints, - float x); - - void updateTfnPaletteTexture(); - - void drawEditor(); - - // callback called whenever transfer function is updated - std::function &)> - transferFunctionUpdatedCallback{nullptr}; - - // all available transfer functions - std::vector tfnsNames; - std::vector> tfnsColorPoints; - std::vector> tfnsOpacityPoints; - std::vector tfnsEditable; - - // properties of currently selected transfer function - int currentMap{0}; - std::vector *tfnColorPoints; - std::vector *tfnOpacityPoints; - bool tfnEditable{true}; - - // flag indicating transfer function has changed in UI - bool tfnChanged{true}; - - // scaling factor for generated opacities - float globalOpacityScale{1.f}; - - // domain (value range) of transfer function - range1f valueRange{-1.f, 1.f}; - - // texture for displaying transfer function color palette - GLuint tfnPaletteTexture{0}; - - // widget name (use different names to support multiple concurrent widgets) - std::string widgetName; -}; diff --git a/examples/interactive/window/VKLWindow.cpp b/examples/interactive/window/VKLWindow.cpp deleted file mode 100644 index 8c0a2d17..00000000 --- a/examples/interactive/window/VKLWindow.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include "VKLWindow.h" -// std -#include -#include -// rkcommon -#include "rkcommon/utility/SaveImage.h" - -#include "renderers/DensityPathTracer.h" -#include "renderers/HitIterator.h" -#include "renderers/IntervalIteratorDebug.h" -#include "renderers/RayMarchIterator.h" - -namespace openvkl { - namespace examples { - - VKLWindow::VKLWindow(const vec2i &windowSize, - const Scene& scene, - std::string rendererType) - : windowSize(windowSize), scene(scene) - { - assert(scene.volume); - const auto volumeBounds = vklGetBoundingBox(scene.volume); - arcballCamera = std::unique_ptr( - new ArcballCamera((const box3f &)volumeBounds, windowSize)); - - renderer_density_pathtracer = - std::unique_ptr(new DensityPathTracer()); - renderer_density_pathtracer->commit(); - renderer_hit_iterator = - std::unique_ptr(new HitIterator()); - renderer_hit_iterator->commit(); - renderer_ray_march_iterator = - std::unique_ptr(new RayMarchIterator()); - renderer_ray_march_iterator->commit(); - renderer_interval_iterator_debug = - std::unique_ptr(new IntervalIteratorDebug()); - renderer_interval_iterator_debug->commit(); - - setActiveRenderer(rendererType); - reshape(this->windowSize); - } - - void VKLWindow::render() - { - if (scene.volume) - { - if (useISPC) - renderer->renderFrame_ispc(scene); - else - renderer->renderFrame(scene); - } - } - - Renderer &VKLWindow::currentRenderer() - { - return *renderer; - } - - void VKLWindow::resetAccumulation() - { - renderer->resetAccumulation(); - } - - void VKLWindow::resetCamera() - { - if (scene.volume) - { - const auto volumeBounds = vklGetBoundingBox(scene.volume); - arcballCamera->resetCamera((const box3f &)volumeBounds); - updateCamera(); - } - } - - void VKLWindow::setUseISPC(bool enabled) - { - useISPC = enabled; - } - - void VKLWindow::savePPM(const std::string &filename) - { - const auto &fb = renderer->frameBuffer(); - utility::writePFM(filename, windowSize.x, windowSize.y, fb.data()); - } - - void VKLWindow::setActiveRenderer(const std::string &rendererType) - { - if (rendererType == "density_pathtracer") - renderer = renderer_density_pathtracer.get(); - else if (rendererType == "hit_iterator") - renderer = renderer_hit_iterator.get(); - else if (rendererType == "ray_march_iterator") - renderer = renderer_ray_march_iterator.get(); - else if (rendererType == "interval_iterator_debug") - renderer = renderer_interval_iterator_debug.get(); - else - throw std::runtime_error("VKLWindow: unknown renderer type"); - - updateCamera(); - } - - void VKLWindow::reshape(const vec2i &newWindowSize) - { - windowSize = newWindowSize; - - renderer_density_pathtracer->setFrameSize(windowSize); - renderer_hit_iterator->setFrameSize(windowSize); - renderer_ray_march_iterator->setFrameSize(windowSize); - renderer_interval_iterator_debug->setFrameSize(windowSize); - - // update camera - arcballCamera->updateWindowSize(windowSize); - updateCamera(); - } - - void VKLWindow::setRenderPixelRange(const region2i &pixelRange) - { - if (area(pixelRange) <= 0) { - throw std::runtime_error("VKLWindow: bad pixelRange"); - } - - region2i windowSizeRange(0, windowSize); - region2i clampedPixelRange = intersectionOf(windowSizeRange, pixelRange); - - if (clampedPixelRange != pixelRange) { - std::cerr << "warning: clamped requested pixelRange to window bounds" - << std::endl; - } - - renderer_density_pathtracer->setPixelRange(clampedPixelRange); - renderer_hit_iterator->setPixelRange(clampedPixelRange); - renderer_ray_march_iterator->setPixelRange(clampedPixelRange); - renderer_interval_iterator_debug->setPixelRange(clampedPixelRange); - } - - void VKLWindow::updateCamera() - { - resetAccumulation(); - - renderer->setCamera(arcballCamera->eyePos(), - arcballCamera->lookDir(), - arcballCamera->upDir(), - windowSize.x / float(windowSize.y)); - } - - } // namespace examples -} // namespace openvkl diff --git a/examples/interactive/window/VKLWindow.h b/examples/interactive/window/VKLWindow.h deleted file mode 100644 index ddf7f7d6..00000000 --- a/examples/interactive/window/VKLWindow.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "ArcballCamera.h" -#include "renderers/Renderer.h" -// rkcommon -#include "rkcommon/math/box.h" -// std -#include - -namespace openvkl { - namespace examples { - - class VKLWindow - { - public: - VKLWindow(const vec2i &windowSize, - const Scene& scene, - std::string rendererType); - - virtual ~VKLWindow() = default; - - void render(); - - Renderer ¤tRenderer(); - - void resetAccumulation(); - - void resetCamera(); - - void setUseISPC(bool enabled); - - void savePPM(const std::string &filename); - - void setActiveRenderer(const std::string &rendererType); - - void setRenderPixelRange(const region2i &pixelRange); - - protected: - virtual void reshape(const vec2i &newWindowSize); - - void updateCamera(); - - bool useISPC{true}; - - vec2i windowSize; - - Renderer *renderer; - - std::unique_ptr renderer_density_pathtracer; - std::unique_ptr renderer_hit_iterator; - std::unique_ptr renderer_ray_march_iterator; - std::unique_ptr renderer_interval_iterator_debug; - - std::unique_ptr arcballCamera; - - const Scene& scene; - }; - - } // namespace examples -} // namespace openvkl diff --git a/gitlab/.gitlab-ci.yml b/gitlab/.gitlab-ci.yml index f8722ec2..43ad9e19 100644 --- a/gitlab/.gitlab-ci.yml +++ b/gitlab/.gitlab-ci.yml @@ -4,7 +4,7 @@ variables: GIT_DEPTH: "15" KW_PROJECT_NAME: openvkl - OPENVKL_RELEASE_PACKAGE_VERSION: "1.0.1" + OPENVKL_RELEASE_PACKAGE_VERSION: "1.1.0" MACOSX_DEPLOYMENT_TARGET: "10.13" stages: @@ -77,11 +77,6 @@ stages: - ./build/openvkl/build/vklTutorialISPC - ./build/openvkl/build/vklTests --durations yes -.job_template: &base_benchmark_job - stage: benchmark - script: - - gitlab/run-benchmarks.sh - .job_template: &base_release_job stage: release before_script: @@ -92,6 +87,35 @@ stages: .job_template: &base_release_test_job stage: release-test +## Job Templates: Benchmarks ## + +.job_template: &base_benchmark_job + stage: benchmark + script: + - gitlab/run-benchmarks.sh + +# the below can be merged in to run only specific benchmark suites + +.job_template: &benchmark_select_structured + variables: + BENCHMARK_SUITE: ^StructuredVolume$ + +.job_template: &benchmark_select_structured-multi + variables: + BENCHMARK_SUITE: ^StructuredVolumeMulti$ + +.job_template: &benchmark_select_vdb + variables: + BENCHMARK_SUITE: ^VDBVolume$ + +.job_template: &benchmark_select_vdb-multi + variables: + BENCHMARK_SUITE: ^VDBVolumeMulti$ + +.job_template: &benchmark_select_examples + variables: + BENCHMARK_SUITE: ^ExampleRenderers$ + ## Build Jobs ## build-centos7: @@ -194,7 +218,7 @@ build-arch-TBB2021: <<: *build_job_docker image: $DOCKER_REGISTRY/ospray/docker-images:arch script: - - gitlab/build.sh -D TBB_VERSION=2021.3.0 -D TBB_HASH="" + - gitlab/build.sh -D TBB_VERSION=2021.4.0 -D TBB_HASH="" build-macOS: <<: *base_build_job @@ -209,7 +233,7 @@ build-macOS-TBB2021: <<: *base_build_job stage: build script: - - gitlab/build.sh -D TBB_VERSION=2021.3.0 -D TBB_HASH="" -DBUILD_TBB_FROM_SOURCE=ON + - gitlab/build.sh -D TBB_VERSION=2021.4.0 -D TBB_HASH="" -DBUILD_TBB_FROM_SOURCE=ON tags: - osx - clang @@ -218,7 +242,7 @@ build-macOS-arm-TBB2021: <<: *base_build_job stage: build script: - - gitlab/build.sh -D TBB_VERSION=2021.3.0 -D TBB_HASH="" -DBUILD_TBB_FROM_SOURCE=ON + - gitlab/build.sh -D TBB_VERSION=2021.4.0 -D TBB_HASH="" -DBUILD_TBB_FROM_SOURCE=ON tags: - mac-arm @@ -237,7 +261,7 @@ build-windows-msvc15: build-windows-msvc15-TBB2021: stage: build script: - - gitlab\build.bat "Visual Studio 15 2017 Win64" "v141" '"-DTBB_VERSION=2021.3.0"' '"-DTBB_HASH="""' + - gitlab\build.bat "Visual Studio 15 2017 Win64" "v141" '"-DTBB_VERSION=2021.4.0"' '"-DTBB_HASH="""' tags: - msvc15 - win10 @@ -407,33 +431,23 @@ test-functional-windows-msvc15-TBB2021: tags: - vis-perf-x8280-1 -benchmark-x8280-1: +all-x8280-1: <<: *manual_benchmark_x8280-1 -structured-benchmark-x8280-1: - <<: *manual_benchmark_x8280-1 - variables: - BENCHMARK_SUITE: ^StructuredVolume$ +structured-x8280-1: + <<: [*manual_benchmark_x8280-1, *benchmark_select_structured] -structured-multi-benchmark-x8280-1: - <<: *manual_benchmark_x8280-1 - variables: - BENCHMARK_SUITE: ^StructuredVolumeMulti$ +structured-multi-x8280-1: + <<: [*manual_benchmark_x8280-1, *benchmark_select_structured-multi] -vdb-benchmark-x8280-1: - <<: *manual_benchmark_x8280-1 - variables: - BENCHMARK_SUITE: ^VDBVolume$ +vdb-x8280-1: + <<: [*manual_benchmark_x8280-1, *benchmark_select_vdb] -vdb-multi-benchmark-x8280-1: - <<: *manual_benchmark_x8280-1 - variables: - BENCHMARK_SUITE: ^VDBVolumeMulti$ +vdb-multi-x8280-1: + <<: [*manual_benchmark_x8280-1, *benchmark_select_vdb-multi] -example-benchmark-x8280-1: - <<: *manual_benchmark_x8280-1 - variables: - BENCHMARK_SUITE: ^ExampleRenderers$ +examples-x8280-1: + <<: [*manual_benchmark_x8280-1, *benchmark_select_examples] # vis-perf-a-1 @@ -445,33 +459,79 @@ example-benchmark-x8280-1: tags: - vis-perf-a-1 -benchmark-a-1: +all-a-1: <<: *manual_benchmark_a-1 -structured-benchmark-a-1: - <<: *manual_benchmark_a-1 - variables: - BENCHMARK_SUITE: ^StructuredVolume$ +structured-a-1: + <<: [*manual_benchmark_a-1, *benchmark_select_structured] -structured-multi-benchmark-a-1: - <<: *manual_benchmark_a-1 - variables: - BENCHMARK_SUITE: ^StructuredVolumeMulti$ +structured-multi-a-1: + <<: [*manual_benchmark_a-1, *benchmark_select_structured-multi] -vdb-benchmark-a-1: - <<: *manual_benchmark_a-1 - variables: - BENCHMARK_SUITE: ^VDBVolume$ +vdb-a-1: + <<: [*manual_benchmark_a-1, *benchmark_select_vdb] -vdb-multi-benchmark-a-1: - <<: *manual_benchmark_a-1 - variables: - BENCHMARK_SUITE: ^VDBVolumeMulti$ +vdb-multi-a-1: + <<: [*manual_benchmark_a-1, *benchmark_select_vdb-multi] -example-benchmark-a-1: - <<: *manual_benchmark_a-1 - variables: - BENCHMARK_SUITE: ^ExampleRenderers$ +examples-a-1: + <<: [*manual_benchmark_a-1, *benchmark_select_examples] + +# vis-sdp-adl-1-perf + +.job_template: &manual_benchmark_adl-1 + <<: *base_benchmark_job + when: manual + needs: + - build-centos7-icc + tags: + - vis-sdp-adl-1-perf + +all-adl-1: + <<: *manual_benchmark_adl-1 + +structured-adl-1: + <<: [*manual_benchmark_adl-1, *benchmark_select_structured] + +structured-multi-adl-1: + <<: [*manual_benchmark_adl-1, *benchmark_select_structured-multi] + +vdb-adl-1: + <<: [*manual_benchmark_adl-1, *benchmark_select_vdb] + +vdb-multi-adl-1: + <<: [*manual_benchmark_adl-1, *benchmark_select_vdb-multi] + +examples-adl-1: + <<: [*manual_benchmark_adl-1, *benchmark_select_examples] + +# vis-sdp-dg2-01-perf + +.job_template: &manual_benchmark_tgl-1 + <<: *base_benchmark_job + when: manual + needs: + - build-centos7-icc + tags: + - vis-sdp-dg2-01-perf + +all-tgl-1: + <<: *manual_benchmark_tgl-1 + +structured-tgl-1: + <<: [*manual_benchmark_tgl-1, *benchmark_select_structured] + +structured-multi-tgl-1: + <<: [*manual_benchmark_tgl-1, *benchmark_select_structured-multi] + +vdb-tgl-1: + <<: [*manual_benchmark_tgl-1, *benchmark_select_vdb] + +vdb-multi-tgl-1: + <<: [*manual_benchmark_tgl-1, *benchmark_select_vdb-multi] + +examples-tgl-1: + <<: [*manual_benchmark_tgl-1, *benchmark_select_examples] # Benchmark jobs meant for scheduled (nightly, weekly, ...) builds. # The builds must define RUN_SCHEDULE_BENCHMARKS to run the job, and may use @@ -496,6 +556,26 @@ benchmark-a-1-schedule: tags: - vis-perf-a-1 +benchmark-adl-1-schedule: + <<: *base_benchmark_job + only: + variables: + - $RUN_SCHEDULE_BENCHMARKS + needs: + - build-centos7-icc + tags: + - vis-sdp-adl-1-perf + +benchmark-tgl-1-schedule: + <<: *base_benchmark_job + only: + variables: + - $RUN_SCHEDULE_BENCHMARKS + needs: + - build-centos7-icc + tags: + - vis-sdp-dg2-01-perf + ## KW Jobs ## build-kw-scan: diff --git a/openvkl/devices/cpu/math/box_utility.ih b/openvkl/devices/cpu/math/box_utility.ih index c36744a4..4b9933ff 100644 --- a/openvkl/devices/cpu/math/box_utility.ih +++ b/openvkl/devices/cpu/math/box_utility.ih @@ -4,7 +4,7 @@ #pragma once #include "rkcommon/math/box.ih" -#include "math/vec_utility.ih" +#include "rkcommon/math/vec.ih" inline bool isempty1f(const box1f &box) { diff --git a/openvkl/devices/cpu/math/math_utility.ih b/openvkl/devices/cpu/math/math_utility.ih deleted file mode 100644 index 57bf9fa5..00000000 --- a/openvkl/devices/cpu/math/math_utility.ih +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -inline uniform float divide_safe(uniform float f) -{ - return 1.f / ((abs(f) < 1e-8f) ? (f >= 0.f ? 1e-8f : -1e-8f) : f); -} - -inline varying float divide_safe(varying float f) -{ - return 1.f / ((abs(f) < 1e-8f) ? (f >= 0.f ? 1e-8f : -1e-8f) : f); -} diff --git a/openvkl/devices/cpu/math/vec_utility.ih b/openvkl/devices/cpu/math/vec_utility.ih deleted file mode 100644 index 84590260..00000000 --- a/openvkl/devices/cpu/math/vec_utility.ih +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "math_utility.ih" -#include "rkcommon/math/vec.ih" - -#define __lift_unary_fct(F) \ - inline uniform vec2f F(const uniform vec2f v) \ - { return make_vec2f(F(v.x),F(v.y)); } \ - inline vec2f F(const vec2f v) \ - { return make_vec2f(F(v.x),F(v.y)); } \ - inline uniform vec3f F(const uniform vec3f v) \ - { return make_vec3f(F(v.x),F(v.y),F(v.z)); } \ - inline vec3f F(const vec3f v) \ - { return make_vec3f(F(v.x),F(v.y),F(v.z)); } \ - inline uniform vec4f F(const uniform vec4f v) \ - { return make_vec4f(F(v.x),F(v.y),F(v.z),F(v.w)); } \ - inline vec4f F(const vec4f v) \ - { return make_vec4f(F(v.x),F(v.y),F(v.z),F(v.w)); } - -__lift_unary_fct(divide_safe) - -#undef __lift_unary_fct diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt index c10603e4..f1899bdd 100644 --- a/superbuild/CMakeLists.txt +++ b/superbuild/CMakeLists.txt @@ -117,14 +117,14 @@ option(BUILD_TBB "Build Intel Threading Building Blocks or search in environment option(BUILD_TBB_FROM_SOURCE "Build Intel Threading Building Blocks from source or use pre-built version?" OFF) if (BUILD_TBB) if (BUILD_TBB_FROM_SOURCE) - set(TBB_VERSION "2021.3.0" CACHE STRING "TBB version to download") + set(TBB_VERSION "2021.4.0" CACHE STRING "TBB version to download") if (TBB_VERSION VERSION_LESS 2021) message(FATAL_ERROR "Only TBB 2021 and later are supported when building TBB from source") endif() string(REGEX REPLACE "(^[0-9]+\.[0-9]+\.[0-9]+$)" "v\\1" TBB_ARCHIVE ${TBB_VERSION}) set(_TBB_URL "https://github.com/oneapi-src/oneTBB/archive/refs/tags/${TBB_ARCHIVE}.zip") - set(_TBB_HASH "aadd36731bdc38702868303a8d08a7e34c5beb54031c99976533e2ca18d4e9ed") + set(_TBB_HASH "08ade531be2e4e904eb6bec8e01da51eb3b0e4e86738128eb2722b95e3fcb5e6") else() set(TBB_VERSION "2020.3" CACHE STRING "TBB version to download") @@ -155,10 +155,10 @@ endif() option(BUILD_RKCOMMON "Build rkcommon or search in environment?" ON) if (BUILD_RKCOMMON) - set(RKCOMMON_VERSION "v1.7.0" CACHE STRING "rkcommon version to download") + set(RKCOMMON_VERSION "v1.8.0" CACHE STRING "rkcommon version to download") set(RKCOMMON_URL "https://github.com/ospray/rkcommon/archive/${RKCOMMON_VERSION}.zip" CACHE STRING "URL of the rkcommon archive.") - set(RKCOMMON_HASH "b2eabd1dc56fd9de7cae320711ae07696385dc3151a56b63562396d0f5e2708e" + set(RKCOMMON_HASH "d9593de25a9b705d7d56f53d655c71901a25be21d88aa493d39d2d6e61ed7393" CACHE STRING "SHA256 hash of the rkcommon archive.") include(dep_rkcommon) endif() @@ -168,23 +168,23 @@ endif() option(BUILD_EMBREE "Build Intel Embree or search in environment?" ON) option(BUILD_EMBREE_FROM_SOURCE "Build Embree from source or use pre-built version? (Only used when BUILD_EMBREE=ON)" ON) if (BUILD_EMBREE) - set(EMBREE_VERSION "v3.13.1" CACHE STRING "Embree version to download") + set(EMBREE_VERSION "v3.13.2" CACHE STRING "Embree version to download") if (BUILD_EMBREE_FROM_SOURCE) set(_EMBREE_URL "https://github.com/embree/embree/archive/${EMBREE_VERSION}.zip") - set(_EMBREE_HASH "51c7fd6cbef79fd49b5a7fd2a1150922a369d183ab6ce5a6845cc73d2308b33d") + set(_EMBREE_HASH "eaa7a8ecd78594fb9eed75b2abbabd30dd68afb49556c250799daaeec016237c") else() # Embree binary package URLs do not use the "v" prefix string(REPLACE "v" "" EMBREE_VERSION_NUMBER ${EMBREE_VERSION}) set(EMBREE_BASE_URL "https://github.com/embree/embree/releases/download/${EMBREE_VERSION}") if (APPLE) set(_EMBREE_URL "${EMBREE_BASE_URL}/embree-${EMBREE_VERSION_NUMBER}.x86_64.macosx.zip") - set(_EMBREE_HASH "1bac7a2a63798a3d04e009d93de06f39875c48c86333f8435db8ea052db2e8ea") + set(_EMBREE_HASH "6e9442e516cd54c2e7f6454c90fb8cda5721d76a14d29880ffa387820a486762") elseif (WIN32) set(_EMBREE_URL "${EMBREE_BASE_URL}/embree-${EMBREE_VERSION_NUMBER}.x64.vc14.windows.zip") - set(_EMBREE_HASH "5590ec5d4971d2c022cbb1d950f3a3b9666ec193c08b42eecf7decf5b5fd3833") + set(_EMBREE_HASH "76570583a3d3e78f74b3cde2b0bbff8b0cc527959cc68b9e501b295e3aa7a960") else() set(_EMBREE_URL "${EMBREE_BASE_URL}/embree-${EMBREE_VERSION_NUMBER}.x86_64.linux.tar.gz") - set(_EMBREE_HASH "7ad9ae04124acad4c4746a9d80d54b457cf078e381383bbb482c3e25e007c70b") + set(_EMBREE_HASH "8142c1fa0e8e89e279581e873f558b008a3d49b9b1e0091393e50377bcc52639") endif() endif() set(EMBREE_URL "${_EMBREE_URL}" CACHE STRING "URL of the Embree source archive.") diff --git a/testing/apps/tests/hit_iterator.cpp b/testing/apps/tests/hit_iterator.cpp index f42f5caf..e64372d4 100644 --- a/testing/apps/tests/hit_iterator.cpp +++ b/testing/apps/tests/hit_iterator.cpp @@ -141,8 +141,7 @@ TEST_CASE("Hit iterator", "[hit_iterators]") new ZVdbVolumeFloat(getOpenVKLDevice(), dimensions, gridOrigin, - gridSpacing, - VKL_FILTER_TRILINEAR)); + gridSpacing)); VKLVolume vklVolume = v->getVKLVolume(getOpenVKLDevice()); @@ -162,7 +161,6 @@ TEST_CASE("Hit iterator", "[hit_iterators]") dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, VKL_DATA_DEFAULT, true, TemporalConfig())); diff --git a/testing/apps/tests/hit_iterator_epsilon.cpp b/testing/apps/tests/hit_iterator_epsilon.cpp index d8caade0..94ea2703 100644 --- a/testing/apps/tests/hit_iterator_epsilon.cpp +++ b/testing/apps/tests/hit_iterator_epsilon.cpp @@ -193,8 +193,7 @@ TEST_CASE("Hit iterator epsilon", "[hit_iterators]") std::make_shared(getOpenVKLDevice(), dimensions, gridOrigin, - gridSpacing, - VKL_FILTER_TRILINEAR)); + gridSpacing)); testingVolumes.push_back(std::make_shared>( dimensions, gridOrigin, gridSpacing)); diff --git a/testing/apps/tests/interval_iterator.cpp b/testing/apps/tests/interval_iterator.cpp index fcd3dd70..03f2eae5 100644 --- a/testing/apps/tests/interval_iterator.cpp +++ b/testing/apps/tests/interval_iterator.cpp @@ -366,8 +366,7 @@ TEST_CASE("Interval iterator", "[interval_iterators]") rkcommon::make_unique(getOpenVKLDevice(), dimensions, gridOrigin, - gridSpacing, - VKL_FILTER_TRILINEAR); + gridSpacing); scalar_single_attribute_interval_iterator_tests(v); } @@ -400,7 +399,6 @@ TEST_CASE("Interval iterator", "[interval_iterators]") dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, VKL_DATA_DEFAULT, true, TemporalConfig())); diff --git a/testing/apps/tests/sampling_utility.h b/testing/apps/tests/sampling_utility.h index a6b8a242..5e85cfcb 100644 --- a/testing/apps/tests/sampling_utility.h +++ b/testing/apps/tests/sampling_utility.h @@ -206,12 +206,15 @@ inline void sampling_on_vertices_vs_procedural_values_multi( std::shared_ptr v, vec3i step = vec3i(1), int lowerSpan = 0, - int upperSpan = 0) + int upperSpan = 0, + VKLFilter filter = VKL_FILTER_TRILINEAR) { const float sampleTolerance = 1e-4f; VKLVolume vklVolume = v->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", filter); + vklSetInt(vklSampler, "gradientFilter", filter); vklCommit(vklSampler); multidim_index_sequence<3> mis(v->getDimensions() / step); diff --git a/testing/apps/tests/vdb_volume.cpp b/testing/apps/tests/vdb_volume.cpp index 54fd270b..e3eff823 100644 --- a/testing/apps/tests/vdb_volume.cpp +++ b/testing/apps/tests/vdb_volume.cpp @@ -82,47 +82,12 @@ TEST_CASE("VDB volume value range", "[value_range]") // half - SECTION("WaveletVdbVolumeHalf nearest") - { - WaveletVdbVolumeHalf *volume = nullptr; - range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_NEAREST)); - REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); - REQUIRE(valueRange.upper >= valueRange.lower); - REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == - Approx(6.f).epsilon(0.001f)); - REQUIRE_NOTHROW(delete volume); - } - - SECTION("WaveletVdbVolumeHalf trilinear") - { - WaveletVdbVolumeHalf *volume = nullptr; - range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); - REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); - REQUIRE(valueRange.upper >= valueRange.lower); - REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == - Approx(6.f).epsilon(0.001f)); - REQUIRE_NOTHROW(delete volume); - } - - SECTION("WaveletVdbVolumeHalf tricubic") + SECTION("WaveletVdbVolumeHalf") { WaveletVdbVolumeHalf *volume = nullptr; range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRICUBIC)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); REQUIRE(valueRange.upper >= valueRange.lower); REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == @@ -132,47 +97,12 @@ TEST_CASE("VDB volume value range", "[value_range]") // float - SECTION("WaveletVdbVolumeFloat nearest") + SECTION("WaveletVdbVolumeFloat") { WaveletVdbVolumeFloat *volume = nullptr; range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_NEAREST)); - REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); - REQUIRE(valueRange.upper >= valueRange.lower); - REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == - Approx(6.f).epsilon(0.001f)); - REQUIRE_NOTHROW(delete volume); - } - - SECTION("WaveletVdbVolumeFloat trilinear") - { - WaveletVdbVolumeFloat *volume = nullptr; - range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); - REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); - REQUIRE(valueRange.upper >= valueRange.lower); - REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == - Approx(6.f).epsilon(0.001f)); - REQUIRE_NOTHROW(delete volume); - } - - SECTION("WaveletVdbVolumeFloat tricubic") - { - WaveletVdbVolumeFloat *volume = nullptr; - range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRICUBIC)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); REQUIRE_NOTHROW(valueRange = volume->getComputedValueRange()); REQUIRE(valueRange.upper >= valueRange.lower); REQUIRE(std::fabs((valueRange.upper - valueRange.lower)) == @@ -192,14 +122,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeHalf nearest") { WaveletVdbVolumeHalf *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_NEAREST)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_NEAREST); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_NEAREST); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -229,14 +158,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeHalf trilinear") { WaveletVdbVolumeHalf *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRILINEAR); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRILINEAR); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -266,14 +194,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeHalf tricubic") { WaveletVdbVolumeHalf *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRICUBIC)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRICUBIC); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRICUBIC); vklCommit(vklSampler); const vec3i step(2); @@ -316,14 +243,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeFloat nearest") { WaveletVdbVolumeFloat *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_NEAREST)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_NEAREST); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_NEAREST); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -353,14 +279,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeFloat trilinear") { WaveletVdbVolumeFloat *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRILINEAR); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRILINEAR); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -390,14 +315,13 @@ TEST_CASE("VDB volume sampling", "[volume_sampling]") SECTION("WaveletVdbVolumeFloat tricubic") { WaveletVdbVolumeFloat *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRICUBIC)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRICUBIC); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRICUBIC); vklCommit(vklSampler); const vec3i step(2); @@ -444,14 +368,13 @@ TEST_CASE("VDB volume interval iterator", "[volume_sampling]") WaveletVdbVolumeFloat *volume = nullptr; range1f valueRange; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRILINEAR); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRILINEAR); vklCommit(vklSampler); VKLIntervalIteratorContext intervalContext = vklNewIntervalIteratorContext(vklSampler); @@ -487,11 +410,12 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") REQUIRE_NOTHROW(volume = new WaveletVdbVolumeHalf(getOpenVKLDevice(), 128, vec3f(0.f), - vec3f(0.01f), - VKL_FILTER_NEAREST)); + vec3f(0.01f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_NEAREST); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_NEAREST); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -526,11 +450,12 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") REQUIRE_NOTHROW(volume = new XYZVdbVolumeHalf(getOpenVKLDevice(), dim, vec3f(0.f), - vec3f(0.01f), - VKL_FILTER_TRILINEAR)); + vec3f(0.01f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRILINEAR); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRILINEAR); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -576,11 +501,12 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") REQUIRE_NOTHROW(volume = new XYZVdbVolumeHalf(getOpenVKLDevice(), dim, vec3f(0.f), - vec3f(0.01f), - VKL_FILTER_TRICUBIC)); + vec3f(0.01f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRICUBIC); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRICUBIC); vklCommit(vklSampler); const vec3i step(2); @@ -625,17 +551,16 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") // float - SECTION("WaveletVdbVolumeFloat nearest") + SECTION("WaveletVdbVolumeFloat") { WaveletVdbVolumeFloat *volume = nullptr; - REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat(getOpenVKLDevice(), - 128, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_NEAREST)); + REQUIRE_NOTHROW(volume = new WaveletVdbVolumeFloat( + getOpenVKLDevice(), 128, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_NEAREST); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_NEAREST); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -666,14 +591,13 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") { XYZVdbVolumeFloat *volume = nullptr; const int dim = 128; - REQUIRE_NOTHROW(volume = new XYZVdbVolumeFloat(getOpenVKLDevice(), - dim, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); + REQUIRE_NOTHROW(volume = new XYZVdbVolumeFloat( + getOpenVKLDevice(), dim, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRILINEAR); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRILINEAR); vklCommit(vklSampler); const vec3i step(2); multidim_index_sequence<3> mis(volume->getDimensions() / step); @@ -715,14 +639,13 @@ TEST_CASE("VDB volume gradients", "[volume_gradients]") { XYZVdbVolumeFloat *volume = nullptr; const int dim = 128; - REQUIRE_NOTHROW(volume = new XYZVdbVolumeFloat(getOpenVKLDevice(), - dim, - vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRICUBIC)); + REQUIRE_NOTHROW(volume = new XYZVdbVolumeFloat( + getOpenVKLDevice(), dim, vec3f(0.f), vec3f(1.f))); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_TRICUBIC); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_TRICUBIC); vklCommit(vklSampler); const vec3i step(2); @@ -793,7 +716,6 @@ TEST_CASE("VDB volume strides", "[volume_strides]") 128, vec3f(0.f), vec3f(1.f), - VKL_FILTER_TRILINEAR, TemporalConfig(), 1, dcf, @@ -832,6 +754,53 @@ TEST_CASE("VDB volume strides", "[volume_strides]") shutdownOpenVKL(); } +void vdb_special_case_interval_iterator( + VKLIntervalIteratorContext intervalContext, + const vkl_vec3f &rayOrigin, + const vkl_vec3f &rayDirection) +{ + std::vector buffer(vklGetIntervalIteratorSize(intervalContext)); + + const vkl_range1f rayTRange = {0.f, inf}; + const float time = 0.f; + + VKLIntervalIterator intervalIterator = + vklInitIntervalIterator(intervalContext, + &rayOrigin, + &rayDirection, + &rayTRange, + time, + buffer.data()); + + int numIntervalsFound = 0; + VKLInterval prevInterval; + + while (true) { + VKLInterval interval; + int result = vklIterateInterval(intervalIterator, &interval); + if (!result) + break; + + INFO("tRange = " << interval.tRange.lower << " " << interval.tRange.upper + << "\nvalueRange = " << interval.valueRange.lower << " " + << interval.valueRange.upper + << "\nnominalDeltaT = " << interval.nominalDeltaT); + + REQUIRE(interval.tRange.lower >= 0.f); + REQUIRE(interval.tRange.upper >= 0.f); + REQUIRE(interval.tRange.upper > interval.tRange.lower); + + if (numIntervalsFound > 0) { + REQUIRE(interval.tRange.lower == prevInterval.tRange.upper); + } + + numIntervalsFound++; + prevInterval = interval; + } + + REQUIRE(numIntervalsFound > 0); +} + TEST_CASE("VDB volume special cases", "[interval_iterators]") { initializeOpenVKL(); @@ -850,53 +819,41 @@ TEST_CASE("VDB volume special cases", "[interval_iterators]") vklNewIntervalIteratorContext(sampler); vklCommit(intervalContext); - std::vector buffer(vklGetIntervalIteratorSize(intervalContext)); - // failure case found from OSPRay { // intbits() representation of ray - const uint32_t rayOrigin[] = {1112900070, 1116163650, 1103628776}; - const uint32_t rayDirection[] = {1081551625, 1098411576, 2984533223}; - - const vkl_range1f rayTRange = {0.f, inf}; - const float time = 0.f; - - VKLIntervalIterator intervalIterator = - vklInitIntervalIterator(intervalContext, - (vkl_vec3f *)&rayOrigin, - (vkl_vec3f *)&rayDirection, - &rayTRange, - time, - buffer.data()); - - int numIntervalsFound = 0; - VKLInterval prevInterval; - - while (true) { - VKLInterval interval; - int result = vklIterateInterval(intervalIterator, &interval); - if (!result) - break; - - INFO("tRange = " << interval.tRange.lower << " " - << interval.tRange.upper - << "\nvalueRange = " << interval.valueRange.lower - << " " << interval.valueRange.upper - << "\nnominalDeltaT = " << interval.nominalDeltaT); - - REQUIRE(interval.tRange.lower >= 0.f); - REQUIRE(interval.tRange.upper >= 0.f); - REQUIRE(interval.tRange.upper > interval.tRange.lower); - - if (numIntervalsFound > 0) { - REQUIRE(interval.tRange.lower == prevInterval.tRange.upper); - } + const uint32_t rayOrigin[] = { + 1112900070, 1116163650, 1103628776}; // 53.3769 67.6528 25.0048 + const uint32_t rayDirection[] = { + 1081551625, 1098411576, 2984533223}; // 3.862 15.5269 -6.64624e-09 - numIntervalsFound++; - prevInterval = interval; - } + vdb_special_case_interval_iterator(intervalContext, + *(vkl_vec3f *)&rayOrigin, + *(vkl_vec3f *)&rayDirection); + + // additional cases based on the above testing updated divide_safe() + // implementation - REQUIRE(numIntervalsFound > 0); + // from rkCommon: smallest positive normal number 2^-126=0x1p-126 (needs a + // C++17 compiler) + static const float rkcommon_flt_min = 1.17549435e-38; + + const vkl_vec3f rayDirectionVec3f = *(vkl_vec3f *)&rayDirection; + + std::vector dirZs{0.f, + rkcommon_flt_min, + 1.1f * rkcommon_flt_min, + -0.f, + -rkcommon_flt_min, + -1.1f * rkcommon_flt_min}; + + for (const auto &dirZ : dirZs) { + vkl_vec3f dir = rayDirectionVec3f; + dir.z = dirZ; + + vdb_special_case_interval_iterator( + intervalContext, *(vkl_vec3f *)&rayOrigin, dir); + } } vklRelease(intervalContext); diff --git a/testing/apps/tests/vdb_volume_inner_node_observer.cpp b/testing/apps/tests/vdb_volume_inner_node_observer.cpp index d28591da..f3ccccf0 100644 --- a/testing/apps/tests/vdb_volume_inner_node_observer.cpp +++ b/testing/apps/tests/vdb_volume_inner_node_observer.cpp @@ -54,6 +54,8 @@ inline void inner_node_tests(VKLVolume vklVolume, const unsigned int numAttributes = vklGetNumAttributes(vklVolume); VKLSampler vklSampler = vklNewSampler(vklVolume); + vklSetInt(vklSampler, "filter", VKL_FILTER_NEAREST); + vklSetInt(vklSampler, "gradientFilter", VKL_FILTER_NEAREST); vklCommit(vklSampler); // the inner node observer returns _child_ nodes of nodes at the specified @@ -146,8 +148,7 @@ TEST_CASE("VDB volume inner node observer", "[volume_observers]") rkcommon::make_unique(getOpenVKLDevice(), dimensions, gridOrigin, - gridSpacing, - VKL_FILTER_NEAREST); + gridSpacing); inner_node_tests(v->getVKLVolume(getOpenVKLDevice()), dimensions, @@ -159,8 +160,7 @@ TEST_CASE("VDB volume inner node observer", "[volume_observers]") auto v = rkcommon::make_unique(getOpenVKLDevice(), dimensions, gridOrigin, - gridSpacing, - VKL_FILTER_NEAREST); + gridSpacing); inner_node_tests(v->getVKLVolume(getOpenVKLDevice()), dimensions, @@ -175,7 +175,6 @@ TEST_CASE("VDB volume inner node observer", "[volume_observers]") dimensions, gridOrigin, gridSpacing, - VKL_FILTER_NEAREST, VKL_DATA_DEFAULT, true)); diff --git a/testing/apps/tests/vdb_volume_motion_blur.cpp b/testing/apps/tests/vdb_volume_motion_blur.cpp index f90e49c8..1b226c7b 100644 --- a/testing/apps/tests/vdb_volume_motion_blur.cpp +++ b/testing/apps/tests/vdb_volume_motion_blur.cpp @@ -28,7 +28,6 @@ TEST_CASE("VDB volume motion blur", "[volume_sampling]") 128, vec3f(0.f), vec3f(1.f), - VKL_FILTER_NEAREST, temporalConfigs[tc])); VKLVolume vklVolume = volume->getVKLVolume(getOpenVKLDevice()); VKLSampler vklSampler = vklNewSampler(vklVolume); diff --git a/testing/apps/tests/vdb_volume_multi.cpp b/testing/apps/tests/vdb_volume_multi.cpp index 99df55a4..4ad1be8a 100644 --- a/testing/apps/tests/vdb_volume_multi.cpp +++ b/testing/apps/tests/vdb_volume_multi.cpp @@ -58,13 +58,12 @@ TEST_CASE("VDB volume multiple attributes", "[volume_multi_attributes]") dimensions, gridOrigin, gridSpacing, - filter, dcf, aos)); num_attributes(v); sampling_on_vertices_vs_procedural_values_multi( - v, step, lowerSpan, upperSpan); + v, step, lowerSpan, upperSpan, filter); // higher gradient tolerance for half due to precision issues gradients_on_vertices_vs_procedural_values_multi(v, step, 0.3f); @@ -88,13 +87,12 @@ TEST_CASE("VDB volume multiple attributes", "[volume_multi_attributes]") dimensions, gridOrigin, gridSpacing, - filter, dcf, aos)); num_attributes(v); sampling_on_vertices_vs_procedural_values_multi( - v, step, lowerSpan, upperSpan); + v, step, lowerSpan, upperSpan, filter); gradients_on_vertices_vs_procedural_values_multi(v, step); for (unsigned int i = 0; i < v->getNumAttributes(); i++) { diff --git a/testing/apps/tests/vectorized_gradients.cpp b/testing/apps/tests/vectorized_gradients.cpp index 4dce0c1e..c1294fa1 100644 --- a/testing/apps/tests/vectorized_gradients.cpp +++ b/testing/apps/tests/vectorized_gradients.cpp @@ -130,8 +130,7 @@ TEST_CASE("Vectorized gradients", "[volume_gradients]") new XYZVdbVolumeFloat(getOpenVKLDevice(), vec3i(128), vec3f(0.f), - vec3f(1.f), - VKL_FILTER_TRILINEAR)); + vec3f(1.f))); VKLVolume volume = v->getVKLVolume(getOpenVKLDevice()); diff --git a/testing/apps/vklBenchmarkVdbVolumeMulti.cpp b/testing/apps/vklBenchmarkVdbVolumeMulti.cpp index bc6e9c64..26117a2a 100644 --- a/testing/apps/vklBenchmarkVdbVolumeMulti.cpp +++ b/testing/apps/vklBenchmarkVdbVolumeMulti.cpp @@ -31,7 +31,6 @@ struct Vdb vec3i(128), vec3f(0.f), vec3f(1.f), - filter, VKL_DATA_SHARED_BUFFER, true)); diff --git a/testing/volume/OpenVdbVolume.h b/testing/volume/OpenVdbVolume.h index 4d7f576c..ede2cfa7 100644 --- a/testing/volume/OpenVdbVolume.h +++ b/testing/volume/OpenVdbVolume.h @@ -20,7 +20,6 @@ namespace openvkl { static OpenVdbVolume *loadVdbFile(VKLDevice device, const std::string &filename, const std::string &field, - VKLFilter filter, bool deferLeaves = false); virtual ~OpenVdbVolume() {} @@ -43,9 +42,8 @@ namespace openvkl { OpenVdbVolumeImpl(VKLDevice device, const std::string &filename, const std::string &field, - VKLFilter filter, bool deferLeaves = false) - : grid(device, filename, field, deferLeaves), filter(filter) + : grid(device, filename, field, deferLeaves) { } @@ -85,7 +83,7 @@ namespace openvkl { rkcommon::utility::CodeTimer commitTimer; commitTimer.start(); - result.volume = grid.createVolume(filter); + result.volume = grid.createVolume(); commitTimer.stop(); result.commitMS = commitTimer.milliseconds(); @@ -142,12 +140,11 @@ namespace openvkl { "specified device not compatible with grid device"); } - volume = grid.createVolume(filter); + volume = grid.createVolume(); } private: Grid grid; - VKLFilter filter{VKL_FILTER_TRILINEAR}; uint64_t lastLoadMS{30}; std::unique_ptr> asyncLoader; }; @@ -161,7 +158,6 @@ namespace openvkl { VKLDevice device, const std::string &filename, const std::string &field, - VKLFilter filter, bool deferLeaves) { openvdb::initialize(); @@ -169,14 +165,21 @@ namespace openvkl { openvdb::io::File file(filename.c_str()); file.open(); + if (!file.hasGrid(field)) { + std::ostringstream os; + os << "invalid field '" << field << "'. valid field names are:"; + for (auto it = file.beginName(); it != file.endName(); ++it) { + os << " '" << *it << "'"; + } + throw std::runtime_error(os.str().c_str()); + } + openvdb::GridBase::Ptr baseGrid = file.readGridMetadata(field); if (baseGrid->valueType() == "float") { - return new OpenVdbFloatVolume( - device, filename, field, filter, deferLeaves); + return new OpenVdbFloatVolume(device, filename, field, deferLeaves); } else if (baseGrid->valueType() == "vec3s") { - return new OpenVdbVec3sVolume( - device, filename, field, filter, deferLeaves); + return new OpenVdbVec3sVolume(device, filename, field, deferLeaves); } else { throw std::runtime_error("unsupported OpenVDB grid type: " + baseGrid->valueType()); @@ -199,7 +202,6 @@ namespace openvkl { static OpenVdbVolume *loadVdbFile(VKLDevice device, const std::string &filename, const std::string &field, - VKLFilter filter, bool deferLeaves = false) { throw std::runtime_error( @@ -217,7 +219,6 @@ namespace openvkl { { OpenVdbVolumeImpl(const std::string &filename, const std::string &field, - VKLFilter filter, bool deferLeaves = false) { throw std::runtime_error( diff --git a/testing/volume/ProceduralVdbVolume.h b/testing/volume/ProceduralVdbVolume.h index f7e000d6..1bbad935 100644 --- a/testing/volume/ProceduralVdbVolume.h +++ b/testing/volume/ProceduralVdbVolume.h @@ -92,7 +92,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter = VKL_FILTER_TRILINEAR, const TemporalConfig &temporalConfig = TemporalConfig(), uint32_t numAttributes = 1, VKLDataCreationFlags dataCreationFlags = VKL_DATA_DEFAULT, @@ -138,7 +137,6 @@ namespace openvkl { private: std::unique_ptr buffers; range1f valueRange; - VKLFilter filter; VKLDataCreationFlags dataCreationFlags; size_t byteStride; uint32_t numAttributes; @@ -461,7 +459,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter, const TemporalConfig &temporalConfig, uint32_t numAttributes, VKLDataCreationFlags dataCreationFlags, @@ -472,7 +469,6 @@ namespace openvkl { getVKLDataType(), temporalConfig), buffers(nullptr), - filter(filter), numAttributes(std::max(numAttributes, 1u)), dataCreationFlags(dataCreationFlags), byteStride(byteStride), @@ -602,7 +598,7 @@ namespace openvkl { "specified device not compatible with VdbVolumeBuffers device"); } - volume = buffers->createVolume(filter); + volume = buffers->createVolume(); } } diff --git a/testing/volume/ProceduralVdbVolumeMulti.h b/testing/volume/ProceduralVdbVolumeMulti.h index c8009164..427c0f17 100644 --- a/testing/volume/ProceduralVdbVolumeMulti.h +++ b/testing/volume/ProceduralVdbVolumeMulti.h @@ -24,7 +24,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter, const std::vector> &attributeVolumes, VKLDataCreationFlags dataCreationFlags, @@ -59,7 +58,6 @@ namespace openvkl { vec3i dimensions; vec3f gridOrigin; vec3f gridSpacing; - VKLFilter filter; std::vector> attributeVolumes; VKLDataCreationFlags dataCreationFlags; bool useAOSLayout; @@ -77,7 +75,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter, const std::vector> &attributeVolumes, VKLDataCreationFlags dataCreationFlags, @@ -85,7 +82,6 @@ namespace openvkl { : dimensions(dimensions), gridOrigin(gridOrigin), gridSpacing(gridSpacing), - filter(filter), attributeVolumes(attributeVolumes), dataCreationFlags(dataCreationFlags), useAOSLayout(useAOSLayout), @@ -372,7 +368,7 @@ namespace openvkl { "specified device not compatible with VdbVolumeBuffers device"); } - volume = buffers->createVolume(filter); + volume = buffers->createVolume(); } } @@ -385,7 +381,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter, VKLDataCreationFlags dataCreationFlags, bool useAOSLayout, TemporalConfig temporalConfig = TemporalConfig()) @@ -400,35 +395,30 @@ namespace openvkl { dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); return new ProceduralVdbVolumeMulti(device, dimensions, gridOrigin, gridSpacing, - filter, volumes, dataCreationFlags, useAOSLayout); @@ -439,7 +429,6 @@ namespace openvkl { const vec3i &dimensions, const vec3f &gridOrigin, const vec3f &gridSpacing, - VKLFilter filter, VKLDataCreationFlags dataCreationFlags, bool useAOSLayout, TemporalConfig temporalConfig = TemporalConfig()) @@ -454,35 +443,30 @@ namespace openvkl { dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); volumes.push_back(std::make_shared(device, dimensions, gridOrigin, gridSpacing, - VKL_FILTER_TRILINEAR, temporalConfig)); return new ProceduralVdbVolumeMulti(device, dimensions, gridOrigin, gridSpacing, - filter, volumes, dataCreationFlags, useAOSLayout); diff --git a/utility/vdb/include/openvkl/utility/vdb/OpenVdbGrid.h b/utility/vdb/include/openvkl/utility/vdb/OpenVdbGrid.h index d76af344..64b3444a 100644 --- a/utility/vdb/include/openvkl/utility/vdb/OpenVdbGrid.h +++ b/utility/vdb/include/openvkl/utility/vdb/OpenVdbGrid.h @@ -273,7 +273,7 @@ namespace openvkl { /* * To creat a VKLVolume, we simply set our parameters and commit. */ - VKLVolume createVolume(VKLFilter filter) const; + VKLVolume createVolume() const; /* * Return the number of nodes in this grid. @@ -357,10 +357,10 @@ namespace openvkl { template inline VKLVolume OpenVdbGrid::createVolume( - VKLFilter filter) const +) const { assert(buffers); - return buffers->createVolume(filter); + return buffers->createVolume(); } template diff --git a/utility/vdb/include/openvkl/utility/vdb/VdbVolumeBuffers.h b/utility/vdb/include/openvkl/utility/vdb/VdbVolumeBuffers.h index ba66d3ad..4f2506f8 100644 --- a/utility/vdb/include/openvkl/utility/vdb/VdbVolumeBuffers.h +++ b/utility/vdb/include/openvkl/utility/vdb/VdbVolumeBuffers.h @@ -110,7 +110,7 @@ namespace openvkl { /* * Create a VKLVolume from these buffers. */ - VKLVolume createVolume(VKLFilter filter) const; + VKLVolume createVolume() const; VKLDevice getVKLDevice() const; @@ -288,6 +288,9 @@ namespace openvkl { assert(false); } else { this->temporalFormat.push_back(VKL_TEMPORAL_FORMAT_CONSTANT); + this->temporallyStructuredNumTimesteps.push_back(0); + this->temporallyUnstructuredIndices.push_back(nullptr); + this->temporallyUnstructuredTimes.push_back(nullptr); } // only use array-of-arrays when we have multiple attributes @@ -447,10 +450,9 @@ namespace openvkl { } } - inline VKLVolume VdbVolumeBuffers::createVolume(VKLFilter filter) const + inline VKLVolume VdbVolumeBuffers::createVolume() const { VKLVolume volume = vklNewVolume(device, "vdb"); - vklSetInt(volume, "filter", filter); VKLData transformData = vklNewData(device, 12, VKL_FLOAT, indexToObject, VKL_DATA_DEFAULT); @@ -465,26 +467,31 @@ namespace openvkl { // object can change safely, including replacing leaf data. // This also means that the VdbVolumeBuffers object can be // destroyed after creating the volume. + assert(level.size() == numNodes); VKLData levelData = vklNewData( device, numNodes, VKL_UINT, level.data(), VKL_DATA_DEFAULT); vklSetData(volume, "node.level", levelData); vklRelease(levelData); + assert(origin.size() == numNodes); VKLData originData = vklNewData( device, numNodes, VKL_VEC3I, origin.data(), VKL_DATA_DEFAULT); vklSetData(volume, "node.origin", originData); vklRelease(originData); + assert(format.size() == numNodes); VKLData formatData = vklNewData( device, numNodes, VKL_UINT, format.data(), VKL_DATA_DEFAULT); vklSetData(volume, "node.format", formatData); vklRelease(formatData); + assert(temporalFormat.size() == numNodes); VKLData temporalFormatData = vklNewData( device, numNodes, VKL_UINT, temporalFormat.data(), VKL_DATA_DEFAULT); vklSetData(volume, "node.temporalFormat", temporalFormatData); vklRelease(temporalFormatData); + assert(temporallyStructuredNumTimesteps.size() == numNodes); VKLData temporallyStructuredNumTimestepsData = vklNewData(device, numNodes, @@ -496,6 +503,7 @@ namespace openvkl { temporallyStructuredNumTimestepsData); vklRelease(temporallyStructuredNumTimestepsData); + assert(temporallyUnstructuredIndices.size() == numNodes); VKLData temporallyUnstructuredIndicesData = vklNewData(device, numNodes, @@ -512,6 +520,7 @@ namespace openvkl { } } + assert(temporallyUnstructuredTimes.size() == numNodes); VKLData temporallyUnstructuredTimesData = vklNewData(device, numNodes, @@ -528,8 +537,9 @@ namespace openvkl { } } + assert(data.size() == numNodes); VKLData dataData = vklNewData( - device, data.size(), VKL_DATA, data.data(), VKL_DATA_DEFAULT); + device, numNodes, VKL_DATA, data.data(), VKL_DATA_DEFAULT); vklSetData(volume, "node.data", dataData); vklRelease(dataData);