├── README.md ├── examples ├── customtextureingui │ ├── CMakeLists.txt │ ├── color.frag │ ├── color.vert │ ├── imguiitem.h │ ├── main.cpp │ ├── main.qml │ ├── triangle.cpp │ └── triangle.h ├── imguiinrhiwidget │ ├── CMakeLists.txt │ ├── color.frag │ ├── color.vert │ └── main.cpp ├── shared │ └── fonts │ │ ├── LICENSE.txt │ │ └── RobotoMono-Medium.ttf ├── simple │ ├── CMakeLists.txt │ ├── imguiitem.h │ ├── main.cpp │ └── main.qml └── simplewindow │ ├── CMakeLists.txt │ ├── color.frag │ ├── color.vert │ └── main.cpp ├── imgui ├── imgui.cmakeinc ├── imgui.frag ├── imgui.vert ├── imgui │ ├── LICENSE.txt │ ├── imconfig.h │ ├── imgui.cpp │ ├── imgui.h │ ├── imgui_demo.cpp │ ├── imgui_draw.cpp │ ├── imgui_internal.h │ ├── imgui_tables.cpp │ ├── imgui_widgets.cpp │ ├── imstb_rectpack.h │ ├── imstb_textedit.h │ └── imstb_truetype.h ├── imguiquick.cmakeinc ├── qrhiimgui.cpp ├── qrhiimgui.h ├── qrhiimguiitem.cpp └── qrhiimguiitem.h └── screenshot.png /README.md: -------------------------------------------------------------------------------- 1 | New integration of Qt Quick and Dear ImGui. Requires Qt 6.4 or newer. 2 | 3 | - This now hides the Qt Quick threading model completely, and is a QQuickItem, 4 | meaning proper input event processing (keyboard focus, stacking order for mouse 5 | events, etc.). 6 | 7 | - The Qt Quick scenegraph integration happens via QSGRenderNode, which we now 8 | try to use in the most optimal way, while getting proper stacking with other 9 | items in the scene. (it won't implicitly disable the renderer's opaque pass as 10 | rendernodes traditionally would, but this is only available with Qt 6.5 and 11 | newer; whereas with older Qt 6 versions it all will still work, just slightly 12 | less optimally) 13 | 14 | - Unlike earlier attempts, the item size and the window device pixel ratio are 15 | respected as well. (NB following the item geometry and transform is still 16 | limited by design, e.g. scaling the item or rotating around any axis will lead 17 | to incorrect scissoring) 18 | 19 | - Applications can subclass QRhiImguiItem, reimplement frame() to do ImGui 20 | stuff (safely on the main thread, even if the Qt Quick rendering has its own 21 | render thread), register the item via the Qt 6 facilities (QML_NAMED_ELEMENT, 22 | qt_add_qml_module), then instantiate somewhere in the QML scene. 23 | 24 | - Input is migrated to the new API in 1.87+. (the key code mapping table may 25 | still be incomplete, though) 26 | 27 | - Additionally, plain QWindow applications or anything that renders with QRhi 28 | (e.g., from Qt 6.7 on, QRhiWidget and QQuickRhiItem) are supported too. 29 | See the simplewindow and imguiinrhiwidget examples. 30 | 31 | ![Screenshot](screenshot.png) 32 | (screenshot of the customtextureingui example) 33 | -------------------------------------------------------------------------------- /examples/customtextureingui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(customtextureingui LANGUAGES CXX) 3 | 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | find_package(Qt6 COMPONENTS Core) 8 | find_package(Qt6 COMPONENTS Gui) 9 | find_package(Qt6 COMPONENTS Quick) 10 | find_package(Qt6 COMPONENTS ShaderTools) 11 | 12 | qt_add_executable(customtextureingui 13 | main.cpp 14 | imguiitem.h 15 | triangle.cpp triangle.h 16 | ) 17 | 18 | set(imgui_base ../../imgui) 19 | set(imgui_target customtextureingui) 20 | include(${imgui_base}/imgui.cmakeinc) 21 | include(${imgui_base}/imguiquick.cmakeinc) 22 | 23 | target_link_libraries(customtextureingui PUBLIC 24 | Qt::Core 25 | Qt::GuiPrivate 26 | Qt::QuickPrivate 27 | ) 28 | 29 | qt_add_qml_module(customtextureingui 30 | URI ImguiTextureExample 31 | VERSION 1.0 32 | QML_FILES 33 | main.qml 34 | RESOURCE_PREFIX 35 | / 36 | NO_RESOURCE_TARGET_PATH 37 | ) 38 | 39 | TARGET_COMPILE_DEFINITIONS(customtextureingui 40 | PRIVATE $<$,$>:QT_QML_DEBUG>) 41 | 42 | qt_add_resources(customtextureingui "resources" 43 | PREFIX 44 | "/" 45 | BASE 46 | "../shared" 47 | FILES 48 | "../shared/fonts/RobotoMono-Medium.ttf" 49 | ) 50 | 51 | qt_add_shaders(customtextureingui "shaders" 52 | PREFIX 53 | "/shaders" 54 | FILES 55 | color.vert 56 | color.frag 57 | ) 58 | -------------------------------------------------------------------------------- /examples/customtextureingui/color.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec3 v_color; 4 | 5 | layout(location = 0) out vec4 fragColor; 6 | 7 | layout(std140, binding = 0) uniform buf { 8 | mat4 mvp; 9 | float opacity; 10 | }; 11 | 12 | void main() 13 | { 14 | fragColor = vec4(v_color * opacity, opacity); 15 | } 16 | -------------------------------------------------------------------------------- /examples/customtextureingui/color.vert: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec4 position; 4 | layout(location = 1) in vec3 color; 5 | 6 | layout(location = 0) out vec3 v_color; 7 | 8 | layout(std140, binding = 0) uniform buf { 9 | mat4 mvp; 10 | float opacity; 11 | }; 12 | 13 | void main() 14 | { 15 | v_color = color; 16 | gl_Position = mvp * position; 17 | } 18 | -------------------------------------------------------------------------------- /examples/customtextureingui/imguiitem.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #ifndef IMGUIITEM_H 5 | #define IMGUIITEM_H 6 | 7 | #include "qrhiimguiitem.h" 8 | 9 | class QRhiTexture; 10 | class QRhiTextureRenderTarget; 11 | class QRhiRenderPassDescriptor; 12 | struct Triangle; 13 | 14 | class CustomRenderer : public QRhiImguiItemCustomRenderer 15 | { 16 | public: 17 | CustomRenderer(QQuickWindow *w) : window(w) { } 18 | ~CustomRenderer(); 19 | void sync(QRhiImguiRenderer *renderer) override; 20 | void render() override; 21 | 22 | QQuickWindow *window; 23 | 24 | struct CustomContent { 25 | QRhiTexture *texture = nullptr; 26 | float dpr = 0; 27 | }; 28 | 29 | CustomContent swPainted; 30 | CustomContent rhiRendered; 31 | 32 | bool swPaintedDirty = false; 33 | Triangle *triangleRenderer = nullptr; 34 | QRhiTextureRenderTarget *triRt = nullptr; 35 | QRhiRenderPassDescriptor *triRpDesc = nullptr; 36 | float triRotation = 0.0f; 37 | }; 38 | 39 | class ImguiItem : public QRhiImguiItem 40 | { 41 | Q_OBJECT 42 | QML_NAMED_ELEMENT(Imgui) 43 | 44 | public: 45 | ImguiItem(); 46 | void frame() override; 47 | QRhiImguiItemCustomRenderer *createCustomRenderer() override; 48 | 49 | CustomRenderer *cr = nullptr; 50 | bool demoWindowOpen = true; 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /examples/customtextureingui/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include 5 | #include 6 | #include 7 | #include "triangle.h" 8 | #include "imguiitem.h" 9 | #include "qrhiimgui.h" 10 | 11 | #include "imgui.h" 12 | 13 | ImguiItem::ImguiItem() 14 | { 15 | ImGuiIO &io(ImGui::GetIO()); 16 | io.IniFilename = nullptr; 17 | 18 | imgui()->rebuildFontAtlasWithFont(QLatin1String(":/fonts/RobotoMono-Medium.ttf")); 19 | } 20 | 21 | static inline ImVec2 unscaledSize(const CustomRenderer::CustomContent &c) 22 | { 23 | const QSize pixelSize = c.texture->pixelSize(); 24 | return ImVec2(pixelSize.width() / c.dpr, pixelSize.height() / c.dpr); 25 | } 26 | 27 | void ImguiItem::frame() 28 | { 29 | ImGui::ShowDemoWindow(&demoWindowOpen); 30 | 31 | ImGui::SetNextWindowPos(ImVec2(100, 350), ImGuiCond_FirstUseEver); 32 | ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); 33 | ImGui::Begin("QPainter -> QImage -> QRhiTexture -> ImGui::Image()", nullptr, ImGuiWindowFlags_HorizontalScrollbar); 34 | 35 | if (cr && cr->swPainted.texture) 36 | ImGui::Image(cr->swPainted.texture, unscaledSize(cr->swPainted)); 37 | 38 | ImGui::End(); 39 | 40 | ImGui::SetNextWindowPos(ImVec2(500, 50), ImGuiCond_FirstUseEver); 41 | ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); 42 | ImGui::Begin("QRhi-based rendering to a QRhiTexture -> Imgui::Image()", nullptr, ImGuiWindowFlags_HorizontalScrollbar); 43 | 44 | if (cr && cr->rhiRendered.texture) 45 | ImGui::Image(cr->rhiRendered.texture, unscaledSize(cr->rhiRendered)); 46 | 47 | ImGui::End(); 48 | } 49 | 50 | QRhiImguiItemCustomRenderer *ImguiItem::createCustomRenderer() 51 | { 52 | // called on the render thread (if there is one) with the main thread blocked 53 | 54 | cr = new CustomRenderer(window()); 55 | return cr; 56 | } 57 | 58 | void CustomRenderer::sync(QRhiImguiRenderer *renderer) 59 | { 60 | // called on the render thread (if there is one) with the main thread blocked 61 | 62 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 63 | QRhi *rhi = window->rhi(); 64 | #else 65 | QSGRendererInterface *rif = window->rendererInterface(); 66 | QRhi *rhi = static_cast(rif->getResource(window, QSGRendererInterface::RhiResource)); 67 | #endif 68 | 69 | if (rhi) { 70 | if (!swPainted.texture) { 71 | swPainted.texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512)); 72 | if (swPainted.texture->create()) { 73 | renderer->registerCustomTexture(swPainted.texture, 74 | swPainted.texture, 75 | QRhiSampler::Linear, 76 | QRhiImguiRenderer::TakeCustomTextureOwnership); 77 | swPainted.dpr = window->effectiveDevicePixelRatio(); 78 | swPaintedDirty = true; 79 | } 80 | } 81 | if (!rhiRendered.texture) { 82 | rhiRendered.texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget); 83 | if (rhiRendered.texture->create()) { 84 | renderer->registerCustomTexture(rhiRendered.texture, 85 | rhiRendered.texture, 86 | QRhiSampler::Linear, 87 | QRhiImguiRenderer::TakeCustomTextureOwnership); 88 | rhiRendered.dpr = window->effectiveDevicePixelRatio(); 89 | triRt = rhi->newTextureRenderTarget({ rhiRendered.texture }); 90 | triRpDesc = triRt->newCompatibleRenderPassDescriptor(); 91 | triRt->setRenderPassDescriptor(triRpDesc); 92 | triRt->create(); 93 | } 94 | } 95 | } 96 | } 97 | 98 | void CustomRenderer::render() 99 | { 100 | // called on the render thread (if there is one) 101 | 102 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 103 | QRhi *rhi = window->rhi(); 104 | QRhiSwapChain *swapchain = window->swapChain(); 105 | #else 106 | QSGRendererInterface *rif = window->rendererInterface(); 107 | QRhi *rhi = static_cast(rif->getResource(window, QSGRendererInterface::RhiResource)); 108 | QRhiSwapChain *swapchain = static_cast(rif->getResource(window, QSGRendererInterface::RhiSwapchainResource)); 109 | #endif 110 | if (!rhi || !swapchain) 111 | return; 112 | 113 | QRhiCommandBuffer *cb = swapchain->currentFrameCommandBuffer(); 114 | 115 | if (swPaintedDirty) { 116 | swPaintedDirty = false; 117 | QImage img(swPainted.texture->pixelSize(), QImage::Format_RGBA8888); 118 | img.setDevicePixelRatio(swPainted.dpr); 119 | 120 | const int textureUnscaledWidth = img.width() / swPainted.dpr; 121 | const int textureUnscaledHeight = img.height() / swPainted.dpr; 122 | 123 | QPainter p(&img); 124 | p.fillRect(0, 0, textureUnscaledWidth, textureUnscaledHeight, Qt::red); 125 | QFont font; 126 | font.setPointSize(16); 127 | p.setFont(font); 128 | p.drawText(10, 50, QLatin1String("Hello world with QPainter in a texture")); 129 | QPen pen(Qt::yellow); 130 | pen.setWidth(4); 131 | p.setPen(pen); 132 | p.drawRect(0, 0, textureUnscaledWidth - 1, textureUnscaledHeight - 1); 133 | p.end(); 134 | 135 | QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); 136 | u->uploadTexture(swPainted.texture, img); 137 | cb->resourceUpdate(u); 138 | } 139 | 140 | if (!triangleRenderer) { 141 | triangleRenderer = new Triangle; 142 | triangleRenderer->init(rhi, cb, triRpDesc); 143 | } 144 | triangleRenderer->render(cb, triRt, Qt::transparent, triRotation, 1.0f); 145 | triRotation += 1.0f; 146 | } 147 | 148 | CustomRenderer::~CustomRenderer() 149 | { 150 | // called on the render thread (if there is one) 151 | 152 | delete triangleRenderer; 153 | delete triRt; 154 | delete triRpDesc; 155 | } 156 | 157 | int main(int argc, char *argv[]) 158 | { 159 | QGuiApplication app(argc, argv); 160 | 161 | qputenv("QSG_INFO", "1"); 162 | 163 | QQuickView view; 164 | view.setColor(Qt::black); 165 | view.setResizeMode(QQuickView::SizeRootObjectToView); 166 | view.resize(1280, 720); 167 | view.setSource(QUrl("qrc:/main.qml")); 168 | view.show(); 169 | 170 | int r = app.exec(); 171 | 172 | return r; 173 | } 174 | -------------------------------------------------------------------------------- /examples/customtextureingui/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import ImguiTextureExample 3 | 4 | Rectangle { 5 | color: "lightGray" 6 | 7 | Rectangle { 8 | color: "green" 9 | width: 300 10 | height: 300 11 | x: 100 12 | y: 100 13 | NumberAnimation on rotation { from: 0; to: 360; duration: 3000; loops: -1 } 14 | Text { text: "This is a Qt Quick scene"; anchors.centerIn: parent } 15 | } 16 | 17 | Imgui { 18 | anchors.fill: parent 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/customtextureingui/triangle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include "triangle.h" 5 | #include 6 | 7 | static float vertexData[] = { 8 | 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 9 | -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 10 | 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 11 | }; 12 | 13 | static QShader getShader(const QString &name) 14 | { 15 | QFile f(name); 16 | return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); 17 | } 18 | 19 | void Triangle::init(QRhi *rhi, QRhiCommandBuffer *cb, QRhiRenderPassDescriptor *rpDesc) 20 | { 21 | m_rhi = rhi; 22 | 23 | m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); 24 | m_vbuf->create(); 25 | 26 | m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68)); 27 | m_ubuf->create(); 28 | 29 | m_srb.reset(m_rhi->newShaderResourceBindings()); 30 | m_srb->setBindings({ 31 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, 32 | m_ubuf.get()) 33 | }); 34 | m_srb->create(); 35 | 36 | m_ps.reset(m_rhi->newGraphicsPipeline()); 37 | 38 | QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; 39 | premulAlphaBlend.enable = true; 40 | m_ps->setTargetBlends({ premulAlphaBlend }); 41 | 42 | m_ps->setShaderStages({ 43 | { QRhiShaderStage::Vertex, getShader(QLatin1String(":/shaders/color.vert.qsb")) }, 44 | { QRhiShaderStage::Fragment, getShader(QLatin1String(":/shaders/color.frag.qsb")) } 45 | }); 46 | 47 | QRhiVertexInputLayout inputLayout; 48 | inputLayout.setBindings({ 49 | { 5 * sizeof(float) } 50 | }); 51 | inputLayout.setAttributes({ 52 | { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, 53 | { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } 54 | }); 55 | 56 | m_ps->setVertexInputLayout(inputLayout); 57 | m_ps->setShaderResourceBindings(m_srb.get()); 58 | m_ps->setRenderPassDescriptor(rpDesc); 59 | 60 | m_ps->create(); 61 | 62 | QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch(); 63 | u->uploadStaticBuffer(m_vbuf.get(), vertexData); 64 | cb->resourceUpdate(u); 65 | } 66 | 67 | void Triangle::render(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &clearColor, float rotation, float opacity) 68 | { 69 | const QSize outputSizeInPixels = rt->pixelSize(); 70 | if (m_lastSize != outputSizeInPixels) { 71 | m_lastSize = outputSizeInPixels; 72 | m_viewProjection = m_rhi->clipSpaceCorrMatrix(); 73 | m_viewProjection.perspective(45.0f, outputSizeInPixels.width() / (float) outputSizeInPixels.height(), 0.01f, 1000.0f); 74 | m_viewProjection.translate(0, 0, -4); 75 | } 76 | 77 | QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch(); 78 | QMatrix4x4 mvp = m_viewProjection; 79 | mvp.rotate(rotation, 0, 1, 0); 80 | u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData()); 81 | u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &opacity); 82 | 83 | cb->beginPass(rt, clearColor, { 1.0f, 0 }, u); 84 | 85 | cb->setGraphicsPipeline(m_ps.get()); 86 | cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); 87 | cb->setShaderResources(); 88 | 89 | const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); 90 | cb->setVertexInput(0, 1, &vbufBinding); 91 | cb->draw(3); 92 | 93 | cb->endPass(); 94 | } 95 | -------------------------------------------------------------------------------- /examples/customtextureingui/triangle.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #ifndef TRIANGLE_H 5 | #define TRIANGLE_H 6 | 7 | #include 8 | 9 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | struct Triangle 16 | { 17 | void init(QRhi *rhi, QRhiCommandBuffer *cb, QRhiRenderPassDescriptor *rpDesc); 18 | void render(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &clearColor, float rotation, float opacity); 19 | 20 | QRhi *m_rhi = nullptr; 21 | 22 | std::unique_ptr m_vbuf; 23 | std::unique_ptr m_ubuf; 24 | std::unique_ptr m_srb; 25 | std::unique_ptr m_ps; 26 | 27 | QSize m_lastSize; 28 | QMatrix4x4 m_viewProjection; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /examples/imguiinrhiwidget/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(imguiinrhiwidget LANGUAGES CXX) 3 | 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | find_package(Qt6 COMPONENTS Core Gui Widgets ShaderTools) 8 | 9 | qt_add_executable(imguiinrhiwidget 10 | main.cpp 11 | ) 12 | 13 | set(imgui_base ../../imgui) 14 | set(imgui_target imguiinrhiwidget) 15 | include(${imgui_base}/imgui.cmakeinc) 16 | 17 | target_link_libraries(imguiinrhiwidget PUBLIC 18 | Qt::Core 19 | Qt::GuiPrivate 20 | Qt::Widgets 21 | ) 22 | 23 | qt6_add_shaders(imguiinrhiwidget "shaders" 24 | PREFIX 25 | "/shaders" 26 | FILES 27 | color.vert 28 | color.frag 29 | ) 30 | 31 | qt6_add_resources(imguiinrhiwidget "resources" 32 | PREFIX 33 | "/" 34 | BASE 35 | "../shared" 36 | FILES 37 | "../shared/fonts/RobotoMono-Medium.ttf" 38 | ) 39 | -------------------------------------------------------------------------------- /examples/imguiinrhiwidget/color.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec3 v_color; 4 | 5 | layout(location = 0) out vec4 fragColor; 6 | 7 | layout(std140, binding = 0) uniform buf { 8 | mat4 mvp; 9 | float opacity; 10 | }; 11 | 12 | void main() 13 | { 14 | fragColor = vec4(v_color * opacity, opacity); 15 | } 16 | -------------------------------------------------------------------------------- /examples/imguiinrhiwidget/color.vert: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec4 position; 4 | layout(location = 1) in vec3 color; 5 | 6 | layout(location = 0) out vec3 v_color; 7 | 8 | layout(std140, binding = 0) uniform buf { 9 | mat4 mvp; 10 | float opacity; 11 | }; 12 | 13 | void main() 14 | { 15 | v_color = color; 16 | gl_Position = mvp * position; 17 | } 18 | -------------------------------------------------------------------------------- /examples/imguiinrhiwidget/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "qrhiimgui.h" 16 | #include "imgui.h" 17 | 18 | static float vertexData[] = { 19 | 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 20 | -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 21 | 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 22 | }; 23 | 24 | static QShader getShader(const QString &name) 25 | { 26 | QFile f(name); 27 | return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); 28 | } 29 | 30 | class Widget : public QRhiWidget 31 | { 32 | public: 33 | Widget(QWidget *parent = nullptr); 34 | 35 | void initialize(QRhiCommandBuffer *cb) override; 36 | void render(QRhiCommandBuffer *cb) override; 37 | 38 | bool event(QEvent *e) override; 39 | 40 | private: 41 | void gui(); 42 | 43 | QRhi *m_rhi = nullptr; 44 | QMatrix4x4 m_proj; 45 | std::unique_ptr m_vbuf; 46 | std::unique_ptr m_ubuf; 47 | std::unique_ptr m_srb; 48 | std::unique_ptr m_ps; 49 | 50 | std::unique_ptr m_imguiRenderer; 51 | QRhiImgui m_imgui; 52 | QMatrix4x4 m_guiMvp; 53 | 54 | float m_rotation = 0; 55 | float m_opacity = 1; 56 | int m_opacityDir = -1; 57 | 58 | bool m_showDemoWindow = true; 59 | }; 60 | 61 | Widget::Widget(QWidget *parent) 62 | : QRhiWidget(parent) 63 | { 64 | // to get mouse moves even when no button is pressed, i.e. to match the QWindow behavior 65 | setMouseTracking(true); 66 | } 67 | 68 | void Widget::initialize(QRhiCommandBuffer *cb) 69 | { 70 | if (!m_imguiRenderer) { 71 | ImGuiIO &io(ImGui::GetIO()); 72 | io.IniFilename = nullptr; 73 | m_imgui.rebuildFontAtlasWithFont(QLatin1String(":/fonts/RobotoMono-Medium.ttf")); 74 | m_imguiRenderer.reset(new QRhiImguiRenderer); 75 | } 76 | if (m_rhi != rhi()) { 77 | m_ps.reset(); 78 | m_rhi = rhi(); 79 | } 80 | if (!m_ps) { 81 | m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); 82 | m_vbuf->create(); 83 | 84 | m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68)); 85 | m_ubuf->create(); 86 | 87 | m_srb.reset(m_rhi->newShaderResourceBindings()); 88 | m_srb->setBindings({ 89 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, 90 | m_ubuf.get()) 91 | }); 92 | m_srb->create(); 93 | 94 | m_ps.reset(m_rhi->newGraphicsPipeline()); 95 | 96 | QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; 97 | premulAlphaBlend.enable = true; 98 | m_ps->setTargetBlends({ premulAlphaBlend }); 99 | 100 | m_ps->setShaderStages({ 101 | { QRhiShaderStage::Vertex, getShader(QLatin1String(":/shaders/color.vert.qsb")) }, 102 | { QRhiShaderStage::Fragment, getShader(QLatin1String(":/shaders/color.frag.qsb")) } 103 | }); 104 | 105 | QRhiVertexInputLayout inputLayout; 106 | inputLayout.setBindings({ 107 | { 5 * sizeof(float) } 108 | }); 109 | inputLayout.setAttributes({ 110 | { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, 111 | { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } 112 | }); 113 | 114 | m_ps->setVertexInputLayout(inputLayout); 115 | m_ps->setShaderResourceBindings(m_srb.get()); 116 | m_ps->setRenderPassDescriptor(renderTarget()->renderPassDescriptor()); 117 | 118 | m_ps->create(); 119 | 120 | QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); 121 | resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData); 122 | cb->resourceUpdate(resourceUpdates); 123 | } 124 | 125 | const QSize outputSize = renderTarget()->pixelSize(); 126 | m_proj = m_rhi->clipSpaceCorrMatrix(); 127 | m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); 128 | m_proj.translate(0, 0, -4); 129 | 130 | m_guiMvp = m_rhi->clipSpaceCorrMatrix(); 131 | const float dpr = devicePixelRatio(); 132 | m_guiMvp.ortho(0, outputSize.width() / dpr, outputSize.height() / dpr, 0, 1, -1); 133 | } 134 | 135 | void Widget::render(QRhiCommandBuffer *cb) 136 | { 137 | m_imgui.nextFrame(size(), devicePixelRatio(), QPointF(0, 0), std::bind(&Widget::gui, this)); 138 | m_imgui.syncRenderer(m_imguiRenderer.get()); 139 | 140 | m_imguiRenderer->prepare(m_rhi, renderTarget(), cb, m_guiMvp, 1.0f); 141 | 142 | QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch(); 143 | m_rotation += 1.0f; 144 | QMatrix4x4 mvp = m_proj; 145 | mvp.rotate(m_rotation, 0, 1, 0); 146 | u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData()); 147 | m_opacity += m_opacityDir * 0.005f; 148 | if (m_opacity < 0.0f || m_opacity > 1.0f) { 149 | m_opacityDir *= -1; 150 | m_opacity = qBound(0.0f, m_opacity, 1.0f); 151 | } 152 | u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &m_opacity); 153 | 154 | const QSize outputSizeInPixels = renderTarget()->pixelSize(); 155 | cb->beginPass(renderTarget(), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); 156 | 157 | cb->setGraphicsPipeline(m_ps.get()); 158 | cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); 159 | cb->setShaderResources(); 160 | 161 | const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); 162 | cb->setVertexInput(0, 1, &vbufBinding); 163 | cb->draw(3); 164 | 165 | m_imguiRenderer->render(); 166 | 167 | cb->endPass(); 168 | 169 | update(); 170 | } 171 | 172 | void Widget::gui() 173 | { 174 | ImGui::ShowDemoWindow(&m_showDemoWindow); 175 | 176 | ImGui::SetNextWindowPos(ImVec2(50, 120), ImGuiCond_FirstUseEver); 177 | ImGui::SetNextWindowSize(ImVec2(400, 100), ImGuiCond_FirstUseEver); 178 | ImGui::Begin("Test"); 179 | ImGui::Text("QRhiWidget (with QRhi backend %s)\nin a QScrollArea in a QVBoxLayout", rhi()->backendName()); 180 | ImGui::End(); 181 | } 182 | 183 | bool Widget::event(QEvent *e) 184 | { 185 | if (m_imgui.processEvent(e)) 186 | return true; 187 | 188 | return QRhiWidget::event(e); 189 | } 190 | 191 | struct ScrollAreaKeyFilter : public QObject 192 | { 193 | ScrollAreaKeyFilter(QObject *target) : m_target(target) { } 194 | bool eventFilter(QObject *obj, QEvent *e) override { 195 | switch (e->type()) { 196 | case QEvent::KeyPress: 197 | case QEvent::KeyRelease: 198 | QCoreApplication::sendEvent(m_target, e); 199 | return true; 200 | default: 201 | break; 202 | } 203 | return QObject::eventFilter(obj, e); 204 | } 205 | QObject *m_target; 206 | }; 207 | 208 | int main(int argc, char **argv) 209 | { 210 | QApplication app(argc, argv); 211 | 212 | QRhiWidget::Api graphicsApi; 213 | #if defined(Q_OS_WIN) 214 | graphicsApi = QRhiWidget::Api::Direct3D11; 215 | #elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) 216 | graphicsApi = QRhiWidget::Api::Metal; 217 | #elif QT_CONFIG(vulkan) 218 | graphicsApi = QRhiWidget::Api::Vulkan; 219 | #else 220 | graphicsApi = QRhiWidget::Api::OpenGL; 221 | #endif 222 | 223 | QCommandLineParser cmdLineParser; 224 | cmdLineParser.addHelpOption(); 225 | QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); 226 | cmdLineParser.addOption(nullOption); 227 | QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL")); 228 | cmdLineParser.addOption(glOption); 229 | QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); 230 | cmdLineParser.addOption(vkOption); 231 | QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11")); 232 | cmdLineParser.addOption(d3d11Option); 233 | QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12")); 234 | cmdLineParser.addOption(d3d12Option); 235 | QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); 236 | cmdLineParser.addOption(mtlOption); 237 | 238 | cmdLineParser.process(app); 239 | if (cmdLineParser.isSet(nullOption)) 240 | graphicsApi = QRhiWidget::Api::Null; 241 | if (cmdLineParser.isSet(glOption)) 242 | graphicsApi = QRhiWidget::Api::OpenGL; 243 | if (cmdLineParser.isSet(vkOption)) 244 | graphicsApi = QRhiWidget::Api::Vulkan; 245 | if (cmdLineParser.isSet(d3d11Option)) 246 | graphicsApi = QRhiWidget::Api::Direct3D11; 247 | if (cmdLineParser.isSet(d3d12Option)) 248 | graphicsApi = QRhiWidget::Api::Direct3D12; 249 | if (cmdLineParser.isSet(mtlOption)) 250 | graphicsApi = QRhiWidget::Api::Metal; 251 | 252 | QVBoxLayout *layout = new QVBoxLayout; 253 | 254 | QScrollArea *scrollArea = new QScrollArea; 255 | Widget *rhiWidget = new Widget; 256 | rhiWidget->resize(1920, 1080); 257 | rhiWidget->setApi(graphicsApi); 258 | qDebug() << rhiWidget->api(); 259 | scrollArea->setWidget(rhiWidget); 260 | layout->addWidget(scrollArea); 261 | 262 | ScrollAreaKeyFilter scrollAreaKeyFilter(rhiWidget); 263 | scrollArea->installEventFilter(&scrollAreaKeyFilter); 264 | 265 | QVBoxLayout *otherContent = new QVBoxLayout; 266 | otherContent->addWidget(new QLabel(QLatin1String("A label"))); 267 | otherContent->addWidget(new QPushButton(QLatin1String("A button"))); 268 | QCheckBox *cb = new QCheckBox(QLatin1String("Show widget a widget on top")); 269 | cb->setChecked(true); 270 | otherContent->addWidget(cb); 271 | layout->addLayout(otherContent); 272 | 273 | QWidget topLevel; 274 | topLevel.setLayout(layout); 275 | topLevel.resize(1280, 720); 276 | 277 | QLabel *overlayLabel = new QLabel(&topLevel); 278 | overlayLabel->setText(QLatin1String("Some QWidget on top\n(to prove stacking and translucency works)")); 279 | overlayLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); 280 | overlayLabel->setAutoFillBackground(true); 281 | QPalette semiTransparent(QColor(255, 0, 0, 64)); 282 | semiTransparent.setBrush(QPalette::Text, Qt::white); 283 | semiTransparent.setBrush(QPalette::WindowText, Qt::white); 284 | overlayLabel->setPalette(semiTransparent); 285 | QFont f = overlayLabel->font(); 286 | f.setPixelSize(QFontInfo(f).pixelSize() * 2); 287 | f.setWeight(QFont::Bold); 288 | overlayLabel->setFont(f); 289 | overlayLabel->resize(600, 150); 290 | overlayLabel->move(200, 500); 291 | 292 | QObject::connect(cb, &QCheckBox::stateChanged, overlayLabel, [overlayLabel, cb] { 293 | overlayLabel->setVisible(cb->isChecked()); 294 | }); 295 | 296 | topLevel.show(); 297 | return app.exec(); 298 | } 299 | -------------------------------------------------------------------------------- /examples/shared/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /examples/shared/fonts/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui2/a2326ae432d6d44ce8fc418e45b40cfb0f69f08e/examples/shared/fonts/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /examples/simple/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(simple LANGUAGES CXX) 3 | 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | find_package(Qt6 COMPONENTS Core) 8 | find_package(Qt6 COMPONENTS Gui) 9 | find_package(Qt6 COMPONENTS Quick) 10 | find_package(Qt6 COMPONENTS ShaderTools) 11 | 12 | qt_add_executable(simple 13 | main.cpp 14 | imguiitem.h 15 | ) 16 | 17 | set(imgui_base ../../imgui) 18 | set(imgui_target simple) 19 | include(${imgui_base}/imgui.cmakeinc) 20 | include(${imgui_base}/imguiquick.cmakeinc) 21 | 22 | target_link_libraries(simple PUBLIC 23 | Qt::Core 24 | Qt::GuiPrivate 25 | Qt::QuickPrivate 26 | ) 27 | 28 | qt_add_qml_module(simple 29 | URI ImguiExample 30 | VERSION 1.0 31 | QML_FILES 32 | main.qml 33 | RESOURCE_PREFIX 34 | / 35 | NO_RESOURCE_TARGET_PATH 36 | ) 37 | 38 | TARGET_COMPILE_DEFINITIONS(simple 39 | PRIVATE $<$,$>:QT_QML_DEBUG>) 40 | -------------------------------------------------------------------------------- /examples/simple/imguiitem.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #ifndef IMGUIITEM_H 5 | #define IMGUIITEM_H 6 | 7 | #include "qrhiimguiitem.h" 8 | 9 | class ImguiItem : public QRhiImguiItem 10 | { 11 | Q_OBJECT 12 | QML_NAMED_ELEMENT(Imgui) 13 | 14 | public: 15 | QVector> callbacks; 16 | void frame() override { 17 | for (auto &f : callbacks) 18 | f(); 19 | } 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /examples/simple/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include 5 | #include 6 | #include 7 | #include "imguiitem.h" 8 | #include "imgui.h" 9 | 10 | namespace Test { 11 | static bool showDemoWindow = true; 12 | 13 | static void frame() 14 | { 15 | ImGuiIO &io(ImGui::GetIO()); 16 | io.FontAllowUserScaling = true; // enable ctrl+wheel on windows 17 | io.IniFilename = nullptr; // no imgui.ini 18 | 19 | ImGui::ShowDemoWindow(&showDemoWindow); 20 | 21 | ImGui::SetNextWindowPos(ImVec2(50, 120), ImGuiCond_FirstUseEver); 22 | ImGui::SetNextWindowSize(ImVec2(400, 100), ImGuiCond_FirstUseEver); 23 | ImGui::Begin("Test"); 24 | static char s[512]; 25 | bool print = false; 26 | if (ImGui::InputText("qDebug", s, sizeof(s), ImGuiInputTextFlags_EnterReturnsTrue)) 27 | print = true; 28 | if (ImGui::Button("Print")) 29 | print = true; 30 | if (print) 31 | qDebug("%s", s); 32 | ImGui::End(); 33 | } 34 | 35 | } // namespace Test 36 | 37 | namespace LogWin { 38 | static QtMessageHandler prevMsgHandler; 39 | static QMutex msgMutex; 40 | static QStringList msgBuf; 41 | static bool msgBufChanged = false; 42 | const int MAX_LOG_MESSAGE_LENGTH = 1000; 43 | const int MAX_LOG_LENGTH = 10000; 44 | static bool scrollOnChange = true; 45 | 46 | static void messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) 47 | { 48 | if (prevMsgHandler) 49 | prevMsgHandler(type, ctx, msg); 50 | 51 | QMutexLocker locker(&msgMutex); 52 | 53 | QString decoratedMsg; 54 | if (ctx.category) { 55 | decoratedMsg += QString::fromUtf8(ctx.category); 56 | decoratedMsg += QLatin1String(": "); 57 | } 58 | decoratedMsg += QStringView(msg).left(MAX_LOG_MESSAGE_LENGTH); 59 | 60 | while (msgBuf.count() > MAX_LOG_LENGTH) 61 | msgBuf.removeFirst(); 62 | 63 | msgBuf.append(decoratedMsg); 64 | msgBufChanged = true; 65 | } 66 | 67 | static bool logWindowOpen = true; 68 | 69 | static void frame() 70 | { 71 | if (!logWindowOpen) 72 | return; 73 | 74 | ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); 75 | ImGui::SetNextWindowPos(ImVec2(100, 300), ImGuiCond_FirstUseEver); 76 | ImGui::Begin("Log", &logWindowOpen, ImGuiWindowFlags_NoSavedSettings); 77 | 78 | QMutexLocker locker(&msgMutex); 79 | 80 | if (ImGui::Button("Clear")) 81 | msgBuf.clear(); 82 | ImGui::SameLine(); 83 | ImGui::Checkbox("Scroll on change", &scrollOnChange); 84 | ImGui::Separator(); 85 | 86 | ImGui::BeginChild("loglist", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); 87 | for (const QString &msg : msgBuf) { 88 | const QByteArray msgBa = msg.toUtf8() + '\n'; 89 | ImGui::TextUnformatted(msgBa.constData()); 90 | } 91 | if (scrollOnChange && msgBufChanged) 92 | ImGui::SetScrollHereY(1.0f); 93 | ImGui::EndChild(); 94 | ImGui::Separator(); 95 | 96 | ImGui::End(); 97 | 98 | msgBufChanged = false; 99 | } 100 | 101 | } // namespace LogWin 102 | 103 | int main(int argc, char *argv[]) 104 | { 105 | QGuiApplication app(argc, argv); 106 | 107 | qputenv("QSG_INFO", "1"); 108 | LogWin::prevMsgHandler = qInstallMessageHandler(LogWin::messageHandler); 109 | 110 | QQuickView view; 111 | view.setColor(Qt::black); 112 | view.setResizeMode(QQuickView::SizeRootObjectToView); 113 | view.resize(1280, 720); 114 | view.setSource(QUrl("qrc:/main.qml")); 115 | view.show(); 116 | 117 | ImguiItem *gui = view.rootObject()->findChild("gui"); 118 | gui->callbacks << Test::frame << LogWin::frame; 119 | 120 | int r = app.exec(); 121 | 122 | return r; 123 | } 124 | -------------------------------------------------------------------------------- /examples/simple/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import ImguiExample 5 | 6 | Item { 7 | Rectangle { 8 | color: "red" 9 | anchors.fill: parent 10 | Rectangle { 11 | id: imguiContainer 12 | property bool fullWin: true 13 | property bool useTex: false 14 | layer.enabled: useTex 15 | color: "transparent" 16 | border.color: "green" 17 | border.width: 2 18 | width: fullWin ? parent.width : parent.width - 200 19 | height: fullWin ? parent.height : parent.height - 400 20 | x: fullWin ? 0 : 100 21 | y: fullWin ? 0 : 100 22 | z: ztimer.running ? 1 : 0 23 | Imgui { 24 | id: gui 25 | objectName: "gui" 26 | anchors.fill: parent 27 | SequentialAnimation on opacity { 28 | id: opacityAnim 29 | running: false 30 | NumberAnimation { from: 1; to: 0; duration: 3000 } 31 | NumberAnimation { from: 0; to: 1; duration: 3000 } 32 | } 33 | } 34 | Timer { 35 | id: ztimer 36 | repeat: false 37 | interval: 10000 38 | } 39 | } 40 | Rectangle { 41 | border.width: 2 42 | border.color: "black" 43 | y: 50 44 | width: 400 45 | height: 50 46 | clip: true 47 | TextEdit { 48 | id: textEdit 49 | anchors.fill: parent 50 | text: "TextEdit to test focus" 51 | font.pointSize: 20 52 | color: "blue" 53 | } 54 | } 55 | RowLayout { 56 | y: 20 57 | Button { 58 | text: "Toggle visibility" 59 | onClicked: gui.visible = !gui.visible 60 | } 61 | Button { 62 | text: "Toggle size" 63 | onClicked: imguiContainer.fullWin = !imguiContainer.fullWin 64 | } 65 | Button { 66 | text: "Animate opacity" 67 | onClicked: opacityAnim.running = true 68 | } 69 | Button { 70 | text: "Move to top for 10 sec" 71 | onClicked: ztimer.running = true 72 | } 73 | Button { 74 | text: "Toggle layer (full size only)" 75 | onClicked: imguiContainer.useTex = !imguiContainer.useTex 76 | } 77 | } 78 | } 79 | Column { 80 | anchors.right: parent.right 81 | Text { 82 | text: "ImGui item visible: " + gui.visible 83 | + "\ncovers entire window: " + imguiContainer.fullWin 84 | + "\npart of an Item layer: " + imguiContainer.useTex 85 | + "\nstacks on top of buttons/textedit: " + ztimer.running 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/simplewindow/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(simplewindow LANGUAGES CXX) 3 | 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | find_package(Qt6 COMPONENTS Core) 8 | find_package(Qt6 COMPONENTS Gui) 9 | find_package(Qt6 COMPONENTS ShaderTools) 10 | 11 | qt_add_executable(simplewindow 12 | main.cpp 13 | ) 14 | 15 | set(imgui_base ../../imgui) 16 | set(imgui_target simplewindow) 17 | include(${imgui_base}/imgui.cmakeinc) 18 | 19 | target_link_libraries(simplewindow PUBLIC 20 | Qt::Core 21 | Qt::GuiPrivate 22 | ) 23 | 24 | qt6_add_shaders(simplewindow "shaders" 25 | PREFIX 26 | "/shaders" 27 | FILES 28 | color.vert 29 | color.frag 30 | ) 31 | 32 | qt6_add_resources(simplewindow "resources" 33 | PREFIX 34 | "/" 35 | BASE 36 | "../shared" 37 | FILES 38 | "../shared/fonts/RobotoMono-Medium.ttf" 39 | ) 40 | -------------------------------------------------------------------------------- /examples/simplewindow/color.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec3 v_color; 4 | 5 | layout(location = 0) out vec4 fragColor; 6 | 7 | layout(std140, binding = 0) uniform buf { 8 | mat4 mvp; 9 | float opacity; 10 | }; 11 | 12 | void main() 13 | { 14 | fragColor = vec4(v_color * opacity, opacity); 15 | } 16 | -------------------------------------------------------------------------------- /examples/simplewindow/color.vert: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec4 position; 4 | layout(location = 1) in vec3 color; 5 | 6 | layout(location = 0) out vec3 v_color; 7 | 8 | layout(std140, binding = 0) uniform buf { 9 | mat4 mvp; 10 | float opacity; 11 | }; 12 | 13 | void main() 14 | { 15 | v_color = color; 16 | gl_Position = mvp * position; 17 | } 18 | -------------------------------------------------------------------------------- /examples/simplewindow/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "qrhiimgui.h" 12 | #include "imgui.h" 13 | 14 | static float vertexData[] = { 15 | 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 16 | -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 17 | 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 18 | }; 19 | 20 | static QShader getShader(const QString &name) 21 | { 22 | QFile f(name); 23 | return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); 24 | } 25 | 26 | struct Window : public QWindow 27 | { 28 | Window(QRhi::Implementation graphicsApi); 29 | 30 | void releaseSwapChain(); 31 | 32 | #if QT_CONFIG(opengl) 33 | std::unique_ptr m_fallbackSurface; 34 | #endif 35 | std::unique_ptr m_rhi; 36 | std::unique_ptr m_sc; 37 | std::unique_ptr m_ds; 38 | std::unique_ptr m_rp; 39 | 40 | bool m_hasSwapChain = false; 41 | QMatrix4x4 m_proj; 42 | 43 | void init(); 44 | void resizeSwapChain(); 45 | void render(); 46 | 47 | void exposeEvent(QExposeEvent *) override; 48 | bool event(QEvent *) override; 49 | 50 | QRhi::Implementation m_graphicsApi; 51 | 52 | bool m_running = false; 53 | bool m_notExposed = false; 54 | bool m_newlyExposed = false; 55 | 56 | std::unique_ptr m_vbuf; 57 | bool m_vbufReady = false; 58 | std::unique_ptr m_ubuf; 59 | std::unique_ptr m_srb; 60 | std::unique_ptr m_ps; 61 | 62 | std::unique_ptr m_imguiRenderer; 63 | QRhiImgui m_imgui; 64 | QMatrix4x4 m_guiMvp; 65 | 66 | float m_rotation = 0; 67 | float m_opacity = 1; 68 | int m_opacityDir = -1; 69 | 70 | void gui(); 71 | bool m_showDemoWindow = true; 72 | }; 73 | 74 | Window::Window(QRhi::Implementation graphicsApi) 75 | : m_graphicsApi(graphicsApi) 76 | { 77 | switch (graphicsApi) { 78 | case QRhi::OpenGLES2: 79 | setSurfaceType(OpenGLSurface); 80 | break; 81 | case QRhi::Vulkan: 82 | setSurfaceType(VulkanSurface); 83 | break; 84 | case QRhi::D3D11: 85 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 86 | case QRhi::D3D12: 87 | #endif 88 | setSurfaceType(Direct3DSurface); 89 | break; 90 | case QRhi::Metal: 91 | setSurfaceType(MetalSurface); 92 | break; 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | void Window::exposeEvent(QExposeEvent *) 99 | { 100 | if (isExposed() && !m_running) { 101 | m_running = true; 102 | init(); 103 | resizeSwapChain(); 104 | } 105 | 106 | const QSize surfaceSize = m_hasSwapChain ? m_sc->surfacePixelSize() : QSize(); 107 | 108 | if ((!isExposed() || (m_hasSwapChain && surfaceSize.isEmpty())) && m_running && !m_notExposed) 109 | m_notExposed = true; 110 | 111 | if (isExposed() && m_running && m_notExposed && !surfaceSize.isEmpty()) { 112 | m_notExposed = false; 113 | m_newlyExposed = true; 114 | } 115 | 116 | if (isExposed() && !surfaceSize.isEmpty()) 117 | render(); 118 | } 119 | 120 | bool Window::event(QEvent *e) 121 | { 122 | switch (e->type()) { 123 | case QEvent::UpdateRequest: 124 | render(); 125 | break; 126 | 127 | case QEvent::PlatformSurface: 128 | if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) 129 | releaseSwapChain(); 130 | break; 131 | 132 | default: 133 | if (m_imgui.processEvent(e)) 134 | return true; 135 | break; 136 | } 137 | 138 | return QWindow::event(e); 139 | } 140 | 141 | void Window::init() 142 | { 143 | QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers 144 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 145 | | QRhi::EnableTimestamps 146 | #else 147 | | QRhi::EnableProfiling 148 | #endif 149 | ; 150 | 151 | if (m_graphicsApi == QRhi::Null) { 152 | QRhiNullInitParams params; 153 | m_rhi.reset(QRhi::create(QRhi::Null, ¶ms, rhiFlags)); 154 | } 155 | 156 | #if QT_CONFIG(opengl) 157 | if (m_graphicsApi == QRhi::OpenGLES2) { 158 | m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface()); 159 | QRhiGles2InitParams params; 160 | params.fallbackSurface = m_fallbackSurface.get(); 161 | params.window = this; 162 | m_rhi.reset(QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags)); 163 | } 164 | #endif 165 | 166 | #if QT_CONFIG(vulkan) 167 | if (m_graphicsApi == QRhi::Vulkan) { 168 | QRhiVulkanInitParams params; 169 | params.inst = vulkanInstance(); 170 | params.window = this; 171 | m_rhi.reset(QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags)); 172 | } 173 | #endif 174 | 175 | #ifdef Q_OS_WIN 176 | if (m_graphicsApi == QRhi::D3D11) { 177 | QRhiD3D11InitParams params; 178 | params.enableDebugLayer = true; 179 | m_rhi.reset(QRhi::create(QRhi::D3D11, ¶ms, rhiFlags)); 180 | } 181 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 182 | else if (m_graphicsApi == QRhi::D3D12) { 183 | QRhiD3D12InitParams params; 184 | params.enableDebugLayer = true; 185 | m_rhi.reset(QRhi::create(QRhi::D3D12, ¶ms, rhiFlags)); 186 | } 187 | #endif 188 | #endif 189 | 190 | #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) 191 | if (m_graphicsApi == QRhi::Metal) { 192 | QRhiMetalInitParams params; 193 | m_rhi.reset(QRhi::create(QRhi::Metal, ¶ms, rhiFlags)); 194 | } 195 | #endif 196 | 197 | if (!m_rhi) 198 | qFatal("Failed to create RHI backend"); 199 | 200 | m_sc.reset(m_rhi->newSwapChain()); 201 | m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, 202 | QSize(), 203 | 1, 204 | QRhiRenderBuffer::UsedWithSwapChainOnly)); 205 | m_sc->setWindow(this); 206 | m_sc->setDepthStencil(m_ds.get()); 207 | m_rp.reset(m_sc->newCompatibleRenderPassDescriptor()); 208 | m_sc->setRenderPassDescriptor(m_rp.get()); 209 | 210 | m_imgui.rebuildFontAtlasWithFont(QLatin1String(":/fonts/RobotoMono-Medium.ttf")); 211 | 212 | m_imguiRenderer.reset(new QRhiImguiRenderer); 213 | 214 | m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); 215 | m_vbuf->create(); 216 | m_vbufReady = false; 217 | 218 | m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68)); 219 | m_ubuf->create(); 220 | 221 | m_srb.reset(m_rhi->newShaderResourceBindings()); 222 | m_srb->setBindings({ 223 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, 224 | m_ubuf.get()) 225 | }); 226 | m_srb->create(); 227 | 228 | m_ps.reset(m_rhi->newGraphicsPipeline()); 229 | 230 | QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; 231 | premulAlphaBlend.enable = true; 232 | m_ps->setTargetBlends({ premulAlphaBlend }); 233 | 234 | const QShader vs = getShader(QLatin1String(":/shaders/color.vert.qsb")); 235 | if (!vs.isValid()) 236 | qFatal("Failed to load shader pack (vertex)"); 237 | const QShader fs = getShader(QLatin1String(":/shaders/color.frag.qsb")); 238 | if (!fs.isValid()) 239 | qFatal("Failed to load shader pack (fragment)"); 240 | 241 | m_ps->setShaderStages({ 242 | { QRhiShaderStage::Vertex, vs }, 243 | { QRhiShaderStage::Fragment, fs } 244 | }); 245 | 246 | QRhiVertexInputLayout inputLayout; 247 | inputLayout.setBindings({ 248 | { 5 * sizeof(float) } 249 | }); 250 | inputLayout.setAttributes({ 251 | { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, 252 | { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } 253 | }); 254 | 255 | m_ps->setVertexInputLayout(inputLayout); 256 | m_ps->setShaderResourceBindings(m_srb.get()); 257 | m_ps->setRenderPassDescriptor(m_rp.get()); 258 | 259 | m_ps->create(); 260 | } 261 | 262 | void Window::resizeSwapChain() 263 | { 264 | m_hasSwapChain = m_sc->createOrResize(); 265 | 266 | const QSize outputSize = m_sc->currentPixelSize(); 267 | m_proj = m_rhi->clipSpaceCorrMatrix(); 268 | m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); 269 | m_proj.translate(0, 0, -4); 270 | 271 | m_guiMvp = m_rhi->clipSpaceCorrMatrix(); 272 | const float dpr = devicePixelRatio(); 273 | m_guiMvp.ortho(0, outputSize.width() / dpr, outputSize.height() / dpr, 0, 1, -1); 274 | } 275 | 276 | void Window::releaseSwapChain() 277 | { 278 | if (m_hasSwapChain) { 279 | m_hasSwapChain = false; 280 | m_sc->destroy(); 281 | } 282 | } 283 | 284 | void Window::render() 285 | { 286 | if (!m_hasSwapChain || m_notExposed) 287 | return; 288 | 289 | if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { 290 | resizeSwapChain(); 291 | if (!m_hasSwapChain) 292 | return; 293 | m_newlyExposed = false; 294 | } 295 | 296 | QRhi::FrameOpResult r = m_rhi->beginFrame(m_sc.get()); 297 | if (r == QRhi::FrameOpSwapChainOutOfDate) { 298 | resizeSwapChain(); 299 | if (!m_hasSwapChain) 300 | return; 301 | r = m_rhi->beginFrame(m_sc.get()); 302 | } 303 | if (r != QRhi::FrameOpSuccess) { 304 | qDebug("beginFrame failed with %d, retry", r); 305 | requestUpdate(); 306 | return; 307 | } 308 | 309 | QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); 310 | QRhiRenderTarget *rt = m_sc->currentFrameRenderTarget(); 311 | const QSize outputSizeInPixels = m_sc->currentPixelSize(); 312 | 313 | m_imgui.nextFrame(size(), devicePixelRatio(), QPointF(0, 0), std::bind(&Window::gui, this)); 314 | m_imgui.syncRenderer(m_imguiRenderer.get()); 315 | 316 | m_imguiRenderer->prepare(m_rhi.get(), rt, cb, m_guiMvp, 1.0f); 317 | 318 | QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch(); 319 | if (!m_vbufReady) { 320 | m_vbufReady = true; 321 | u->uploadStaticBuffer(m_vbuf.get(), vertexData); 322 | } 323 | m_rotation += 1.0f; 324 | QMatrix4x4 mvp = m_proj; 325 | mvp.rotate(m_rotation, 0, 1, 0); 326 | u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData()); 327 | m_opacity += m_opacityDir * 0.005f; 328 | if (m_opacity < 0.0f || m_opacity > 1.0f) { 329 | m_opacityDir *= -1; 330 | m_opacity = qBound(0.0f, m_opacity, 1.0f); 331 | } 332 | u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &m_opacity); 333 | 334 | cb->beginPass(rt, QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); 335 | 336 | cb->setGraphicsPipeline(m_ps.get()); 337 | cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); 338 | cb->setShaderResources(); 339 | 340 | const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); 341 | cb->setVertexInput(0, 1, &vbufBinding); 342 | cb->draw(3); 343 | 344 | m_imguiRenderer->render(); 345 | 346 | cb->endPass(); 347 | 348 | m_rhi->endFrame(m_sc.get()); 349 | 350 | requestUpdate(); 351 | } 352 | 353 | void Window::gui() 354 | { 355 | ImGuiIO &io(ImGui::GetIO()); 356 | io.FontAllowUserScaling = true; // enable ctrl+wheel on windows 357 | io.IniFilename = nullptr; // no imgui.ini 358 | 359 | ImGui::ShowDemoWindow(&m_showDemoWindow); 360 | 361 | ImGui::SetNextWindowPos(ImVec2(50, 120), ImGuiCond_FirstUseEver); 362 | ImGui::SetNextWindowSize(ImVec2(400, 100), ImGuiCond_FirstUseEver); 363 | ImGui::Begin("Test"); 364 | static char s[512]; 365 | bool print = false; 366 | if (ImGui::InputText("qDebug", s, sizeof(s), ImGuiInputTextFlags_EnterReturnsTrue)) 367 | print = true; 368 | if (ImGui::Button("Print")) 369 | print = true; 370 | if (print) 371 | qDebug("%s", s); 372 | ImGui::End(); 373 | } 374 | 375 | static QString graphicsApiName(QRhi::Implementation graphicsApi) 376 | { 377 | switch (graphicsApi) { 378 | case QRhi::Null: 379 | return QLatin1String("Null (no output)"); 380 | case QRhi::OpenGLES2: 381 | return QLatin1String("OpenGL"); 382 | case QRhi::Vulkan: 383 | return QLatin1String("Vulkan"); 384 | case QRhi::D3D11: 385 | return QLatin1String("Direct3D 11"); 386 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 387 | case QRhi::D3D12: 388 | return QLatin1String("Direct3D 12"); 389 | #endif 390 | case QRhi::Metal: 391 | return QLatin1String("Metal"); 392 | default: 393 | break; 394 | } 395 | return QString(); 396 | } 397 | 398 | int main(int argc, char **argv) 399 | { 400 | QGuiApplication app(argc, argv); 401 | 402 | QRhi::Implementation graphicsApi; 403 | #if defined(Q_OS_WIN) 404 | graphicsApi = QRhi::D3D11; 405 | #elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) 406 | graphicsApi = QRhi::Metal; 407 | #elif QT_CONFIG(vulkan) 408 | graphicsApi = QRhi::Vulkan; 409 | #else 410 | graphicsApi = QRhi::OpenGLES2; 411 | #endif 412 | 413 | QCommandLineParser cmdLineParser; 414 | cmdLineParser.addHelpOption(); 415 | QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); 416 | cmdLineParser.addOption(nullOption); 417 | QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL")); 418 | cmdLineParser.addOption(glOption); 419 | QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); 420 | cmdLineParser.addOption(vkOption); 421 | QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11")); 422 | cmdLineParser.addOption(d3d11Option); 423 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 424 | QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12")); 425 | cmdLineParser.addOption(d3d12Option); 426 | #endif 427 | QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); 428 | cmdLineParser.addOption(mtlOption); 429 | 430 | cmdLineParser.process(app); 431 | if (cmdLineParser.isSet(nullOption)) 432 | graphicsApi = QRhi::Null; 433 | if (cmdLineParser.isSet(glOption)) 434 | graphicsApi = QRhi::OpenGLES2; 435 | if (cmdLineParser.isSet(vkOption)) 436 | graphicsApi = QRhi::Vulkan; 437 | if (cmdLineParser.isSet(d3d11Option)) 438 | graphicsApi = QRhi::D3D11; 439 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 440 | if (cmdLineParser.isSet(d3d12Option)) 441 | graphicsApi = QRhi::D3D12; 442 | #endif 443 | if (cmdLineParser.isSet(mtlOption)) 444 | graphicsApi = QRhi::Metal; 445 | 446 | qDebug("Selected graphics API is %s", qPrintable(graphicsApiName(graphicsApi))); 447 | qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); 448 | 449 | QSurfaceFormat fmt; 450 | fmt.setDepthBufferSize(24); 451 | fmt.setStencilBufferSize(8); 452 | QSurfaceFormat::setDefaultFormat(fmt); 453 | 454 | #if QT_CONFIG(vulkan) 455 | QVulkanInstance inst; 456 | if (graphicsApi == QRhi::Vulkan) { 457 | inst.setLayers({ "VK_LAYER_KHRONOS_validation" }); 458 | inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); 459 | if (!inst.create()) { 460 | qWarning("Failed to create Vulkan instance, switching to OpenGL"); 461 | graphicsApi = QRhi::OpenGLES2; 462 | } 463 | } 464 | #endif 465 | 466 | Window w(graphicsApi); 467 | #if QT_CONFIG(vulkan) 468 | if (graphicsApi == QRhi::Vulkan) 469 | w.setVulkanInstance(&inst); 470 | #endif 471 | w.resize(1280, 720); 472 | w.setTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName(graphicsApi)); 473 | w.show(); 474 | 475 | int ret = app.exec(); 476 | 477 | if (w.handle()) 478 | w.releaseSwapChain(); 479 | 480 | return ret; 481 | } 482 | -------------------------------------------------------------------------------- /imgui/imgui.cmakeinc: -------------------------------------------------------------------------------- 1 | set(imgui_sources 2 | ${imgui_base}/imgui/imgui.cpp 3 | ${imgui_base}/imgui/imgui_draw.cpp 4 | ${imgui_base}/imgui/imgui_tables.cpp 5 | ${imgui_base}/imgui/imgui_widgets.cpp 6 | ${imgui_base}/imgui/imgui_demo.cpp 7 | ${imgui_base}/qrhiimgui.cpp 8 | ${imgui_base}/qrhiimgui.h 9 | ) 10 | 11 | target_sources(${imgui_target} PRIVATE 12 | ${imgui_sources} 13 | ) 14 | 15 | target_include_directories(${imgui_target} PRIVATE 16 | ${imgui_base} 17 | ${imgui_base}/imgui 18 | ) 19 | 20 | qt6_add_shaders(${imgui_target} "imgui_shaders" 21 | PREFIX 22 | "/" 23 | BASE 24 | ${imgui_base} 25 | FILES 26 | ${imgui_base}/imgui.vert 27 | ${imgui_base}/imgui.frag 28 | ) 29 | -------------------------------------------------------------------------------- /imgui/imgui.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec2 v_texcoord; 4 | layout(location = 1) in vec4 v_color; 5 | 6 | layout(location = 0) out vec4 fragColor; 7 | 8 | layout(std140, binding = 0) uniform buf { 9 | mat4 mvp; 10 | float opacity; 11 | // Windows HDR: set to SDR_white_level_in_nits / 80 12 | // macOS/iOS EDR: set to 1.0 13 | // No HDR: set to 0.0, will do linear to sRGB at the end then. 14 | float hdrWhiteLevelMult; 15 | }; 16 | 17 | layout(binding = 1) uniform sampler2D tex; 18 | 19 | vec3 linearToSRGB(vec3 color) 20 | { 21 | vec3 S1 = sqrt(color); 22 | vec3 S2 = sqrt(S1); 23 | vec3 S3 = sqrt(S2); 24 | return 0.585122381 * S1 + 0.783140355 * S2 - 0.368262736 * S3; 25 | } 26 | 27 | void main() 28 | { 29 | vec4 c = v_color * texture(tex, v_texcoord); 30 | c.a *= opacity; 31 | if (hdrWhiteLevelMult > 0.0) 32 | c.rgb *= hdrWhiteLevelMult; 33 | else 34 | c.rgb = linearToSRGB(c.rgb); 35 | c.rgb *= c.a; // premultiplied alpha 36 | fragColor = c; 37 | } 38 | -------------------------------------------------------------------------------- /imgui/imgui.vert: -------------------------------------------------------------------------------- 1 | #version 440 2 | 3 | layout(location = 0) in vec4 position; 4 | layout(location = 1) in vec2 texcoord; 5 | layout(location = 2) in vec4 color; 6 | 7 | layout(location = 0) out vec2 v_texcoord; 8 | layout(location = 1) out vec4 v_color; 9 | 10 | layout(std140, binding = 0) uniform buf { 11 | mat4 mvp; 12 | float opacity; 13 | float sdrMult; 14 | }; 15 | 16 | void main() 17 | { 18 | v_texcoord = texcoord; 19 | v_color = color; 20 | gl_Position = mvp * vec4(position.xy, 0.0, 1.0); 21 | } 22 | -------------------------------------------------------------------------------- /imgui/imgui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2022 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /imgui/imgui/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. 19 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 20 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 21 | 22 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 23 | // 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. 24 | // DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() 25 | // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. 26 | //#define IMGUI_API __declspec( dllexport ) 27 | //#define IMGUI_API __declspec( dllimport ) 28 | 29 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 30 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 31 | //#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions. 32 | 33 | //---- Disable all of Dear ImGui or don't implement standard windows/tools. 34 | // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. 35 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 36 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. 37 | //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88). 38 | 39 | //---- Don't implement some functions to reduce linkage requirements. 40 | //#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) 41 | //#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) 42 | //#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) 43 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 44 | //#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). 45 | //#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) 46 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 47 | //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) 48 | //#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. 49 | //#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(). 50 | //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available 51 | 52 | //---- Include imgui_user.h at the end of imgui.h as a convenience 53 | //#define IMGUI_INCLUDE_IMGUI_USER_H 54 | 55 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 56 | //#define IMGUI_USE_BGRA_PACKED_COLOR 57 | 58 | //---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) 59 | //#define IMGUI_USE_WCHAR32 60 | 61 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 62 | // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. 63 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 64 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 65 | //#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled 66 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 67 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 68 | 69 | //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) 70 | // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. 71 | //#define IMGUI_USE_STB_SPRINTF 72 | 73 | //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) 74 | // 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). 75 | // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. 76 | //#define IMGUI_ENABLE_FREETYPE 77 | 78 | //---- Use stb_truetype to build and rasterize the font atlas (default) 79 | // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. 80 | //#define IMGUI_ENABLE_STB_TRUETYPE 81 | 82 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 83 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 84 | /* 85 | #define IM_VEC2_CLASS_EXTRA \ 86 | constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ 87 | operator MyVec2() const { return MyVec2(x,y); } 88 | 89 | #define IM_VEC4_CLASS_EXTRA \ 90 | constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 91 | operator MyVec4() const { return MyVec4(x,y,z,w); } 92 | */ 93 | 94 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 95 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 96 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 97 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 98 | #define ImDrawIdx unsigned int 99 | 100 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 101 | //struct ImDrawList; 102 | //struct ImDrawCmd; 103 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 104 | //#define ImDrawCallback MyImDrawCallback 105 | 106 | //---- Debug Tools: Macro to break in Debugger 107 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 108 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 109 | //#define IM_DEBUG_BREAK __debugbreak() 110 | 111 | //---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), 112 | // (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) 113 | // This adds a small runtime cost which is why it is not enabled by default. 114 | //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX 115 | 116 | //---- Debug Tools: Enable slower asserts 117 | //#define IMGUI_DEBUG_PARANOID 118 | 119 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 120 | /* 121 | namespace ImGui 122 | { 123 | void MyFunction(const char* name, const MyMatrix44& v); 124 | } 125 | */ 126 | -------------------------------------------------------------------------------- /imgui/imgui/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.01. 3 | // Grep for [DEAR IMGUI] to find the changes. 4 | // 5 | // stb_rect_pack.h - v1.01 - public domain - rectangle packing 6 | // Sean Barrett 2014 7 | // 8 | // Useful for e.g. packing rectangular textures into an atlas. 9 | // Does not do rotation. 10 | // 11 | // Before #including, 12 | // 13 | // #define STB_RECT_PACK_IMPLEMENTATION 14 | // 15 | // in the file that you want to have the implementation. 16 | // 17 | // Not necessarily the awesomest packing method, but better than 18 | // the totally naive one in stb_truetype (which is primarily what 19 | // this is meant to replace). 20 | // 21 | // Has only had a few tests run, may have issues. 22 | // 23 | // More docs to come. 24 | // 25 | // No memory allocations; uses qsort() and assert() from stdlib. 26 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 27 | // 28 | // This library currently uses the Skyline Bottom-Left algorithm. 29 | // 30 | // Please note: better rectangle packers are welcome! Please 31 | // implement them to the same API, but with a different init 32 | // function. 33 | // 34 | // Credits 35 | // 36 | // Library 37 | // Sean Barrett 38 | // Minor features 39 | // Martins Mozeiko 40 | // github:IntellectualKitty 41 | // 42 | // Bugfixes / warning fixes 43 | // Jeremy Jaussaud 44 | // Fabian Giesen 45 | // 46 | // Version history: 47 | // 48 | // 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section 49 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 50 | // 0.99 (2019-02-07) warning fixes 51 | // 0.11 (2017-03-03) return packing success/fail result 52 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 53 | // 0.09 (2016-08-27) fix compiler warnings 54 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 55 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 56 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 57 | // 0.05: added STBRP_ASSERT to allow replacing assert 58 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 59 | // 0.01: initial release 60 | // 61 | // LICENSE 62 | // 63 | // See end of file for license information. 64 | 65 | ////////////////////////////////////////////////////////////////////////////// 66 | // 67 | // INCLUDE SECTION 68 | // 69 | 70 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 71 | #define STB_INCLUDE_STB_RECT_PACK_H 72 | 73 | #define STB_RECT_PACK_VERSION 1 74 | 75 | #ifdef STBRP_STATIC 76 | #define STBRP_DEF static 77 | #else 78 | #define STBRP_DEF extern 79 | #endif 80 | 81 | #ifdef __cplusplus 82 | extern "C" { 83 | #endif 84 | 85 | typedef struct stbrp_context stbrp_context; 86 | typedef struct stbrp_node stbrp_node; 87 | typedef struct stbrp_rect stbrp_rect; 88 | 89 | typedef int stbrp_coord; 90 | 91 | #define STBRP__MAXVAL 0x7fffffff 92 | // Mostly for internal use, but this is the maximum supported coordinate value. 93 | 94 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 95 | // Assign packed locations to rectangles. The rectangles are of type 96 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 97 | // are 'num_rects' many of them. 98 | // 99 | // Rectangles which are successfully packed have the 'was_packed' flag 100 | // set to a non-zero value and 'x' and 'y' store the minimum location 101 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 102 | // if you imagine y increasing downwards). Rectangles which do not fit 103 | // have the 'was_packed' flag set to 0. 104 | // 105 | // You should not try to access the 'rects' array from another thread 106 | // while this function is running, as the function temporarily reorders 107 | // the array while it executes. 108 | // 109 | // To pack into another rectangle, you need to call stbrp_init_target 110 | // again. To continue packing into the same rectangle, you can call 111 | // this function again. Calling this multiple times with multiple rect 112 | // arrays will probably produce worse packing results than calling it 113 | // a single time with the full rectangle array, but the option is 114 | // available. 115 | // 116 | // The function returns 1 if all of the rectangles were successfully 117 | // packed and 0 otherwise. 118 | 119 | struct stbrp_rect 120 | { 121 | // reserved for your use: 122 | int id; 123 | 124 | // input: 125 | stbrp_coord w, h; 126 | 127 | // output: 128 | stbrp_coord x, y; 129 | int was_packed; // non-zero if valid packing 130 | 131 | }; // 16 bytes, nominally 132 | 133 | 134 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 135 | // Initialize a rectangle packer to: 136 | // pack a rectangle that is 'width' by 'height' in dimensions 137 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 138 | // 139 | // You must call this function every time you start packing into a new target. 140 | // 141 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 142 | // the following stbrp_pack_rects() call (or calls), but can be freed after 143 | // the call (or calls) finish. 144 | // 145 | // Note: to guarantee best results, either: 146 | // 1. make sure 'num_nodes' >= 'width' 147 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 148 | // 149 | // If you don't do either of the above things, widths will be quantized to multiples 150 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 151 | // 152 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 153 | // may run out of temporary storage and be unable to pack some rectangles. 154 | 155 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 156 | // Optionally call this function after init but before doing any packing to 157 | // change the handling of the out-of-temp-memory scenario, described above. 158 | // If you call init again, this will be reset to the default (false). 159 | 160 | 161 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 162 | // Optionally select which packing heuristic the library should use. Different 163 | // heuristics will produce better/worse results for different data sets. 164 | // If you call init again, this will be reset to the default. 165 | 166 | enum 167 | { 168 | STBRP_HEURISTIC_Skyline_default=0, 169 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 170 | STBRP_HEURISTIC_Skyline_BF_sortHeight 171 | }; 172 | 173 | 174 | ////////////////////////////////////////////////////////////////////////////// 175 | // 176 | // the details of the following structures don't matter to you, but they must 177 | // be visible so you can handle the memory allocations for them 178 | 179 | struct stbrp_node 180 | { 181 | stbrp_coord x,y; 182 | stbrp_node *next; 183 | }; 184 | 185 | struct stbrp_context 186 | { 187 | int width; 188 | int height; 189 | int align; 190 | int init_mode; 191 | int heuristic; 192 | int num_nodes; 193 | stbrp_node *active_head; 194 | stbrp_node *free_head; 195 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 196 | }; 197 | 198 | #ifdef __cplusplus 199 | } 200 | #endif 201 | 202 | #endif 203 | 204 | ////////////////////////////////////////////////////////////////////////////// 205 | // 206 | // IMPLEMENTATION SECTION 207 | // 208 | 209 | #ifdef STB_RECT_PACK_IMPLEMENTATION 210 | #ifndef STBRP_SORT 211 | #include 212 | #define STBRP_SORT qsort 213 | #endif 214 | 215 | #ifndef STBRP_ASSERT 216 | #include 217 | #define STBRP_ASSERT assert 218 | #endif 219 | 220 | #ifdef _MSC_VER 221 | #define STBRP__NOTUSED(v) (void)(v) 222 | #define STBRP__CDECL __cdecl 223 | #else 224 | #define STBRP__NOTUSED(v) (void)sizeof(v) 225 | #define STBRP__CDECL 226 | #endif 227 | 228 | enum 229 | { 230 | STBRP__INIT_skyline = 1 231 | }; 232 | 233 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 234 | { 235 | switch (context->init_mode) { 236 | case STBRP__INIT_skyline: 237 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 238 | context->heuristic = heuristic; 239 | break; 240 | default: 241 | STBRP_ASSERT(0); 242 | } 243 | } 244 | 245 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 246 | { 247 | if (allow_out_of_mem) 248 | // if it's ok to run out of memory, then don't bother aligning them; 249 | // this gives better packing, but may fail due to OOM (even though 250 | // the rectangles easily fit). @TODO a smarter approach would be to only 251 | // quantize once we've hit OOM, then we could get rid of this parameter. 252 | context->align = 1; 253 | else { 254 | // if it's not ok to run out of memory, then quantize the widths 255 | // so that num_nodes is always enough nodes. 256 | // 257 | // I.e. num_nodes * align >= width 258 | // align >= width / num_nodes 259 | // align = ceil(width/num_nodes) 260 | 261 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 262 | } 263 | } 264 | 265 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 266 | { 267 | int i; 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | context->extra[1].y = (1<<30); 287 | context->extra[1].next = NULL; 288 | } 289 | 290 | // find minimum y position if it starts at x1 291 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 292 | { 293 | stbrp_node *node = first; 294 | int x1 = x0 + width; 295 | int min_y, visited_width, waste_area; 296 | 297 | STBRP__NOTUSED(c); 298 | 299 | STBRP_ASSERT(first->x <= x0); 300 | 301 | #if 0 302 | // skip in case we're past the node 303 | while (node->next->x <= x0) 304 | ++node; 305 | #else 306 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 307 | #endif 308 | 309 | STBRP_ASSERT(node->x <= x0); 310 | 311 | min_y = 0; 312 | waste_area = 0; 313 | visited_width = 0; 314 | while (node->x < x1) { 315 | if (node->y > min_y) { 316 | // raise min_y higher. 317 | // we've accounted for all waste up to min_y, 318 | // but we'll now add more waste for everything we've visted 319 | waste_area += visited_width * (node->y - min_y); 320 | min_y = node->y; 321 | // the first time through, visited_width might be reduced 322 | if (node->x < x0) 323 | visited_width += node->next->x - x0; 324 | else 325 | visited_width += node->next->x - node->x; 326 | } else { 327 | // add waste area 328 | int under_width = node->next->x - node->x; 329 | if (under_width + visited_width > width) 330 | under_width = width - visited_width; 331 | waste_area += under_width * (min_y - node->y); 332 | visited_width += under_width; 333 | } 334 | node = node->next; 335 | } 336 | 337 | *pwaste = waste_area; 338 | return min_y; 339 | } 340 | 341 | typedef struct 342 | { 343 | int x,y; 344 | stbrp_node **prev_link; 345 | } stbrp__findresult; 346 | 347 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 348 | { 349 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 350 | stbrp__findresult fr; 351 | stbrp_node **prev, *node, *tail, **best = NULL; 352 | 353 | // align to multiple of c->align 354 | width = (width + c->align - 1); 355 | width -= width % c->align; 356 | STBRP_ASSERT(width % c->align == 0); 357 | 358 | // if it can't possibly fit, bail immediately 359 | if (width > c->width || height > c->height) { 360 | fr.prev_link = NULL; 361 | fr.x = fr.y = 0; 362 | return fr; 363 | } 364 | 365 | node = c->active_head; 366 | prev = &c->active_head; 367 | while (node->x + width <= c->width) { 368 | int y,waste; 369 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 370 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 371 | // bottom left 372 | if (y < best_y) { 373 | best_y = y; 374 | best = prev; 375 | } 376 | } else { 377 | // best-fit 378 | if (y + height <= c->height) { 379 | // can only use it if it first vertically 380 | if (y < best_y || (y == best_y && waste < best_waste)) { 381 | best_y = y; 382 | best_waste = waste; 383 | best = prev; 384 | } 385 | } 386 | } 387 | prev = &node->next; 388 | node = node->next; 389 | } 390 | 391 | best_x = (best == NULL) ? 0 : (*best)->x; 392 | 393 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 394 | // 395 | // e.g, if fitting 396 | // 397 | // ____________________ 398 | // |____________________| 399 | // 400 | // into 401 | // 402 | // | | 403 | // | ____________| 404 | // |____________| 405 | // 406 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 407 | // 408 | // This makes BF take about 2x the time 409 | 410 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 411 | tail = c->active_head; 412 | node = c->active_head; 413 | prev = &c->active_head; 414 | // find first node that's admissible 415 | while (tail->x < width) 416 | tail = tail->next; 417 | while (tail) { 418 | int xpos = tail->x - width; 419 | int y,waste; 420 | STBRP_ASSERT(xpos >= 0); 421 | // find the left position that matches this 422 | while (node->next->x <= xpos) { 423 | prev = &node->next; 424 | node = node->next; 425 | } 426 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 427 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 428 | if (y + height <= c->height) { 429 | if (y <= best_y) { 430 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 431 | best_x = xpos; 432 | //STBRP_ASSERT(y <= best_y); [DEAR IMGUI] 433 | best_y = y; 434 | best_waste = waste; 435 | best = prev; 436 | } 437 | } 438 | } 439 | tail = tail->next; 440 | } 441 | } 442 | 443 | fr.prev_link = best; 444 | fr.x = best_x; 445 | fr.y = best_y; 446 | return fr; 447 | } 448 | 449 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 450 | { 451 | // find best position according to heuristic 452 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 453 | stbrp_node *node, *cur; 454 | 455 | // bail if: 456 | // 1. it failed 457 | // 2. the best node doesn't fit (we don't always check this) 458 | // 3. we're out of memory 459 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 460 | res.prev_link = NULL; 461 | return res; 462 | } 463 | 464 | // on success, create new node 465 | node = context->free_head; 466 | node->x = (stbrp_coord) res.x; 467 | node->y = (stbrp_coord) (res.y + height); 468 | 469 | context->free_head = node->next; 470 | 471 | // insert the new node into the right starting point, and 472 | // let 'cur' point to the remaining nodes needing to be 473 | // stiched back in 474 | 475 | cur = *res.prev_link; 476 | if (cur->x < res.x) { 477 | // preserve the existing one, so start testing with the next one 478 | stbrp_node *next = cur->next; 479 | cur->next = node; 480 | cur = next; 481 | } else { 482 | *res.prev_link = node; 483 | } 484 | 485 | // from here, traverse cur and free the nodes, until we get to one 486 | // that shouldn't be freed 487 | while (cur->next && cur->next->x <= res.x + width) { 488 | stbrp_node *next = cur->next; 489 | // move the current node to the free list 490 | cur->next = context->free_head; 491 | context->free_head = cur; 492 | cur = next; 493 | } 494 | 495 | // stitch the list back in 496 | node->next = cur; 497 | 498 | if (cur->x < res.x + width) 499 | cur->x = (stbrp_coord) (res.x + width); 500 | 501 | #ifdef _DEBUG 502 | cur = context->active_head; 503 | while (cur->x < context->width) { 504 | STBRP_ASSERT(cur->x < cur->next->x); 505 | cur = cur->next; 506 | } 507 | STBRP_ASSERT(cur->next == NULL); 508 | 509 | { 510 | int count=0; 511 | cur = context->active_head; 512 | while (cur) { 513 | cur = cur->next; 514 | ++count; 515 | } 516 | cur = context->free_head; 517 | while (cur) { 518 | cur = cur->next; 519 | ++count; 520 | } 521 | STBRP_ASSERT(count == context->num_nodes+2); 522 | } 523 | #endif 524 | 525 | return res; 526 | } 527 | 528 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 529 | { 530 | const stbrp_rect *p = (const stbrp_rect *) a; 531 | const stbrp_rect *q = (const stbrp_rect *) b; 532 | if (p->h > q->h) 533 | return -1; 534 | if (p->h < q->h) 535 | return 1; 536 | return (p->w > q->w) ? -1 : (p->w < q->w); 537 | } 538 | 539 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 540 | { 541 | const stbrp_rect *p = (const stbrp_rect *) a; 542 | const stbrp_rect *q = (const stbrp_rect *) b; 543 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 544 | } 545 | 546 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 547 | { 548 | int i, all_rects_packed = 1; 549 | 550 | // we use the 'was_packed' field internally to allow sorting/unsorting 551 | for (i=0; i < num_rects; ++i) { 552 | rects[i].was_packed = i; 553 | } 554 | 555 | // sort according to heuristic 556 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 557 | 558 | for (i=0; i < num_rects; ++i) { 559 | if (rects[i].w == 0 || rects[i].h == 0) { 560 | rects[i].x = rects[i].y = 0; // empty rect needs no space 561 | } else { 562 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 563 | if (fr.prev_link) { 564 | rects[i].x = (stbrp_coord) fr.x; 565 | rects[i].y = (stbrp_coord) fr.y; 566 | } else { 567 | rects[i].x = rects[i].y = STBRP__MAXVAL; 568 | } 569 | } 570 | } 571 | 572 | // unsort 573 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 574 | 575 | // set was_packed flags and all_rects_packed status 576 | for (i=0; i < num_rects; ++i) { 577 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 578 | if (!rects[i].was_packed) 579 | all_rects_packed = 0; 580 | } 581 | 582 | // return the all_rects_packed status 583 | return all_rects_packed; 584 | } 585 | #endif 586 | 587 | /* 588 | ------------------------------------------------------------------------------ 589 | This software is available under 2 licenses -- choose whichever you prefer. 590 | ------------------------------------------------------------------------------ 591 | ALTERNATIVE A - MIT License 592 | Copyright (c) 2017 Sean Barrett 593 | Permission is hereby granted, free of charge, to any person obtaining a copy of 594 | this software and associated documentation files (the "Software"), to deal in 595 | the Software without restriction, including without limitation the rights to 596 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 597 | of the Software, and to permit persons to whom the Software is furnished to do 598 | so, subject to the following conditions: 599 | The above copyright notice and this permission notice shall be included in all 600 | copies or substantial portions of the Software. 601 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 602 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 603 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 604 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 605 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 606 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 607 | SOFTWARE. 608 | ------------------------------------------------------------------------------ 609 | ALTERNATIVE B - Public Domain (www.unlicense.org) 610 | This is free and unencumbered software released into the public domain. 611 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 612 | software, either in source code form or as a compiled binary, for any purpose, 613 | commercial or non-commercial, and by any means. 614 | In jurisdictions that recognize copyright laws, the author or authors of this 615 | software dedicate any and all copyright interest in the software to the public 616 | domain. We make this dedication for the benefit of the public at large and to 617 | the detriment of our heirs and successors. We intend this dedication to be an 618 | overt act of relinquishment in perpetuity of all present and future rights to 619 | this software under copyright law. 620 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 621 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 622 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 623 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 624 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 625 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 626 | ------------------------------------------------------------------------------ 627 | */ 628 | -------------------------------------------------------------------------------- /imgui/imgui/imstb_textedit.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_textedit.h 1.14. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) 5 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_textedit.h - v1.14 - public domain - Sean Barrett 8 | // Development of this library was sponsored by RAD Game Tools 9 | // 10 | // This C header file implements the guts of a multi-line text-editing 11 | // widget; you implement display, word-wrapping, and low-level string 12 | // insertion/deletion, and stb_textedit will map user inputs into 13 | // insertions & deletions, plus updates to the cursor position, 14 | // selection state, and undo state. 15 | // 16 | // It is intended for use in games and other systems that need to build 17 | // their own custom widgets and which do not have heavy text-editing 18 | // requirements (this library is not recommended for use for editing large 19 | // texts, as its performance does not scale and it has limited undo). 20 | // 21 | // Non-trivial behaviors are modelled after Windows text controls. 22 | // 23 | // 24 | // LICENSE 25 | // 26 | // See end of file for license information. 27 | // 28 | // 29 | // DEPENDENCIES 30 | // 31 | // Uses the C runtime function 'memmove', which you can override 32 | // by defining STB_TEXTEDIT_memmove before the implementation. 33 | // Uses no other functions. Performs no runtime allocations. 34 | // 35 | // 36 | // VERSION HISTORY 37 | // 38 | // 1.14 (2021-07-11) page up/down, various fixes 39 | // 1.13 (2019-02-07) fix bug in undo size management 40 | // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 41 | // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 42 | // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual 43 | // 1.9 (2016-08-27) customizable move-by-word 44 | // 1.8 (2016-04-02) better keyboard handling when mouse button is down 45 | // 1.7 (2015-09-13) change y range handling in case baseline is non-0 46 | // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 47 | // 1.5 (2014-09-10) add support for secondary keys for OS X 48 | // 1.4 (2014-08-17) fix signed/unsigned warnings 49 | // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 50 | // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 51 | // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 52 | // 1.0 (2012-07-26) improve documentation, initial public release 53 | // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 54 | // 0.2 (2011-11-28) fixes to undo/redo 55 | // 0.1 (2010-07-08) initial version 56 | // 57 | // ADDITIONAL CONTRIBUTORS 58 | // 59 | // Ulf Winklemann: move-by-word in 1.1 60 | // Fabian Giesen: secondary key inputs in 1.5 61 | // Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 62 | // Louis Schnellbach: page up/down in 1.14 63 | // 64 | // Bugfixes: 65 | // Scott Graham 66 | // Daniel Keller 67 | // Omar Cornut 68 | // Dan Thompson 69 | // 70 | // USAGE 71 | // 72 | // This file behaves differently depending on what symbols you define 73 | // before including it. 74 | // 75 | // 76 | // Header-file mode: 77 | // 78 | // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 79 | // it will operate in "header file" mode. In this mode, it declares a 80 | // single public symbol, STB_TexteditState, which encapsulates the current 81 | // state of a text widget (except for the string, which you will store 82 | // separately). 83 | // 84 | // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 85 | // primitive type that defines a single character (e.g. char, wchar_t, etc). 86 | // 87 | // To save space or increase undo-ability, you can optionally define the 88 | // following things that are used by the undo system: 89 | // 90 | // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 91 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 92 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 93 | // 94 | // If you don't define these, they are set to permissive types and 95 | // moderate sizes. The undo system does no memory allocations, so 96 | // it grows STB_TexteditState by the worst-case storage which is (in bytes): 97 | // 98 | // [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT 99 | // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHARCOUNT 100 | // 101 | // 102 | // Implementation mode: 103 | // 104 | // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 105 | // will compile the implementation of the text edit widget, depending 106 | // on a large number of symbols which must be defined before the include. 107 | // 108 | // The implementation is defined only as static functions. You will then 109 | // need to provide your own APIs in the same file which will access the 110 | // static functions. 111 | // 112 | // The basic concept is that you provide a "string" object which 113 | // behaves like an array of characters. stb_textedit uses indices to 114 | // refer to positions in the string, implicitly representing positions 115 | // in the displayed textedit. This is true for both plain text and 116 | // rich text; even with rich text stb_truetype interacts with your 117 | // code as if there was an array of all the displayed characters. 118 | // 119 | // Symbols that must be the same in header-file and implementation mode: 120 | // 121 | // STB_TEXTEDIT_CHARTYPE the character type 122 | // STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 123 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 124 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 125 | // 126 | // Symbols you must define for implementation mode: 127 | // 128 | // STB_TEXTEDIT_STRING the type of object representing a string being edited, 129 | // typically this is a wrapper object with other data you need 130 | // 131 | // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 132 | // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 133 | // starting from character #n (see discussion below) 134 | // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 135 | // to the xpos of the i+1'th char for a line of characters 136 | // starting at character #n (i.e. accounts for kerning 137 | // with previous char) 138 | // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 139 | // (return type is int, -1 means not valid to insert) 140 | // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 141 | // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 142 | // as manually wordwrapping for end-of-line positioning 143 | // 144 | // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 145 | // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 146 | // 147 | // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 148 | // 149 | // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 150 | // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 151 | // STB_TEXTEDIT_K_UP keyboard input to move cursor up 152 | // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 153 | // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 154 | // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 155 | // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 156 | // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 157 | // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 158 | // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 159 | // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 160 | // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 161 | // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 162 | // STB_TEXTEDIT_K_REDO keyboard input to perform redo 163 | // 164 | // Optional: 165 | // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 166 | // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 167 | // required for default WORDLEFT/WORDRIGHT handlers 168 | // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 169 | // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 170 | // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 171 | // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 172 | // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 173 | // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 174 | // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 175 | // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 176 | // 177 | // Keyboard input must be encoded as a single integer value; e.g. a character code 178 | // and some bitflags that represent shift states. to simplify the interface, SHIFT must 179 | // be a bitflag, so we can test the shifted state of cursor movements to allow selection, 180 | // i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 181 | // 182 | // You can encode other things, such as CONTROL or ALT, in additional bits, and 183 | // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 184 | // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 185 | // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 186 | // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 187 | // API below. The control keys will only match WM_KEYDOWN events because of the 188 | // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 189 | // bit so it only decodes WM_CHAR events. 190 | // 191 | // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 192 | // row of characters assuming they start on the i'th character--the width and 193 | // the height and the number of characters consumed. This allows this library 194 | // to traverse the entire layout incrementally. You need to compute word-wrapping 195 | // here. 196 | // 197 | // Each textfield keeps its own insert mode state, which is not how normal 198 | // applications work. To keep an app-wide insert mode, update/copy the 199 | // "insert_mode" field of STB_TexteditState before/after calling API functions. 200 | // 201 | // API 202 | // 203 | // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 204 | // 205 | // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 206 | // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 207 | // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 208 | // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 209 | // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) 210 | // 211 | // Each of these functions potentially updates the string and updates the 212 | // state. 213 | // 214 | // initialize_state: 215 | // set the textedit state to a known good default state when initially 216 | // constructing the textedit. 217 | // 218 | // click: 219 | // call this with the mouse x,y on a mouse down; it will update the cursor 220 | // and reset the selection start/end to the cursor point. the x,y must 221 | // be relative to the text widget, with (0,0) being the top left. 222 | // 223 | // drag: 224 | // call this with the mouse x,y on a mouse drag/up; it will update the 225 | // cursor and the selection end point 226 | // 227 | // cut: 228 | // call this to delete the current selection; returns true if there was 229 | // one. you should FIRST copy the current selection to the system paste buffer. 230 | // (To copy, just copy the current selection out of the string yourself.) 231 | // 232 | // paste: 233 | // call this to paste text at the current cursor point or over the current 234 | // selection if there is one. 235 | // 236 | // key: 237 | // call this for keyboard inputs sent to the textfield. you can use it 238 | // for "key down" events or for "translated" key events. if you need to 239 | // do both (as in Win32), or distinguish Unicode characters from control 240 | // inputs, set a high bit to distinguish the two; then you can define the 241 | // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 242 | // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 243 | // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to 244 | // anything other type you wante before including. 245 | // 246 | // 247 | // When rendering, you can read the cursor position and selection state from 248 | // the STB_TexteditState. 249 | // 250 | // 251 | // Notes: 252 | // 253 | // This is designed to be usable in IMGUI, so it allows for the possibility of 254 | // running in an IMGUI that has NOT cached the multi-line layout. For this 255 | // reason, it provides an interface that is compatible with computing the 256 | // layout incrementally--we try to make sure we make as few passes through 257 | // as possible. (For example, to locate the mouse pointer in the text, we 258 | // could define functions that return the X and Y positions of characters 259 | // and binary search Y and then X, but if we're doing dynamic layout this 260 | // will run the layout algorithm many times, so instead we manually search 261 | // forward in one pass. Similar logic applies to e.g. up-arrow and 262 | // down-arrow movement.) 263 | // 264 | // If it's run in a widget that *has* cached the layout, then this is less 265 | // efficient, but it's not horrible on modern computers. But you wouldn't 266 | // want to edit million-line files with it. 267 | 268 | 269 | //////////////////////////////////////////////////////////////////////////// 270 | //////////////////////////////////////////////////////////////////////////// 271 | //// 272 | //// Header-file mode 273 | //// 274 | //// 275 | 276 | #ifndef INCLUDE_STB_TEXTEDIT_H 277 | #define INCLUDE_STB_TEXTEDIT_H 278 | 279 | //////////////////////////////////////////////////////////////////////// 280 | // 281 | // STB_TexteditState 282 | // 283 | // Definition of STB_TexteditState which you should store 284 | // per-textfield; it includes cursor position, selection state, 285 | // and undo state. 286 | // 287 | 288 | #ifndef STB_TEXTEDIT_UNDOSTATECOUNT 289 | #define STB_TEXTEDIT_UNDOSTATECOUNT 99 290 | #endif 291 | #ifndef STB_TEXTEDIT_UNDOCHARCOUNT 292 | #define STB_TEXTEDIT_UNDOCHARCOUNT 999 293 | #endif 294 | #ifndef STB_TEXTEDIT_CHARTYPE 295 | #define STB_TEXTEDIT_CHARTYPE int 296 | #endif 297 | #ifndef STB_TEXTEDIT_POSITIONTYPE 298 | #define STB_TEXTEDIT_POSITIONTYPE int 299 | #endif 300 | 301 | typedef struct 302 | { 303 | // private data 304 | STB_TEXTEDIT_POSITIONTYPE where; 305 | STB_TEXTEDIT_POSITIONTYPE insert_length; 306 | STB_TEXTEDIT_POSITIONTYPE delete_length; 307 | int char_storage; 308 | } StbUndoRecord; 309 | 310 | typedef struct 311 | { 312 | // private data 313 | StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; 314 | STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; 315 | short undo_point, redo_point; 316 | int undo_char_point, redo_char_point; 317 | } StbUndoState; 318 | 319 | typedef struct 320 | { 321 | ///////////////////// 322 | // 323 | // public data 324 | // 325 | 326 | int cursor; 327 | // position of the text cursor within the string 328 | 329 | int select_start; // selection start point 330 | int select_end; 331 | // selection start and end point in characters; if equal, no selection. 332 | // note that start may be less than or greater than end (e.g. when 333 | // dragging the mouse, start is where the initial click was, and you 334 | // can drag in either direction) 335 | 336 | unsigned char insert_mode; 337 | // each textfield keeps its own insert mode state. to keep an app-wide 338 | // insert mode, copy this value in/out of the app state 339 | 340 | int row_count_per_page; 341 | // page size in number of row. 342 | // this value MUST be set to >0 for pageup or pagedown in multilines documents. 343 | 344 | ///////////////////// 345 | // 346 | // private data 347 | // 348 | unsigned char cursor_at_end_of_line; // not implemented yet 349 | unsigned char initialized; 350 | unsigned char has_preferred_x; 351 | unsigned char single_line; 352 | unsigned char padding1, padding2, padding3; 353 | float preferred_x; // this determines where the cursor up/down tries to seek to along x 354 | StbUndoState undostate; 355 | } STB_TexteditState; 356 | 357 | 358 | //////////////////////////////////////////////////////////////////////// 359 | // 360 | // StbTexteditRow 361 | // 362 | // Result of layout query, used by stb_textedit to determine where 363 | // the text in each row is. 364 | 365 | // result of layout query 366 | typedef struct 367 | { 368 | float x0,x1; // starting x location, end x location (allows for align=right, etc) 369 | float baseline_y_delta; // position of baseline relative to previous row's baseline 370 | float ymin,ymax; // height of row above and below baseline 371 | int num_chars; 372 | } StbTexteditRow; 373 | #endif //INCLUDE_STB_TEXTEDIT_H 374 | 375 | 376 | //////////////////////////////////////////////////////////////////////////// 377 | //////////////////////////////////////////////////////////////////////////// 378 | //// 379 | //// Implementation mode 380 | //// 381 | //// 382 | 383 | 384 | // implementation isn't include-guarded, since it might have indirectly 385 | // included just the "header" portion 386 | #ifdef STB_TEXTEDIT_IMPLEMENTATION 387 | 388 | #ifndef STB_TEXTEDIT_memmove 389 | #include 390 | #define STB_TEXTEDIT_memmove memmove 391 | #endif 392 | 393 | 394 | ///////////////////////////////////////////////////////////////////////////// 395 | // 396 | // Mouse input handling 397 | // 398 | 399 | // traverse the layout to locate the nearest character to a display position 400 | static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) 401 | { 402 | StbTexteditRow r; 403 | int n = STB_TEXTEDIT_STRINGLEN(str); 404 | float base_y = 0, prev_x; 405 | int i=0, k; 406 | 407 | r.x0 = r.x1 = 0; 408 | r.ymin = r.ymax = 0; 409 | r.num_chars = 0; 410 | 411 | // search rows to find one that straddles 'y' 412 | while (i < n) { 413 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 414 | if (r.num_chars <= 0) 415 | return n; 416 | 417 | if (i==0 && y < base_y + r.ymin) 418 | return 0; 419 | 420 | if (y < base_y + r.ymax) 421 | break; 422 | 423 | i += r.num_chars; 424 | base_y += r.baseline_y_delta; 425 | } 426 | 427 | // below all text, return 'after' last character 428 | if (i >= n) 429 | return n; 430 | 431 | // check if it's before the beginning of the line 432 | if (x < r.x0) 433 | return i; 434 | 435 | // check if it's before the end of the line 436 | if (x < r.x1) { 437 | // search characters in row for one that straddles 'x' 438 | prev_x = r.x0; 439 | for (k=0; k < r.num_chars; ++k) { 440 | float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 441 | if (x < prev_x+w) { 442 | if (x < prev_x+w/2) 443 | return k+i; 444 | else 445 | return k+i+1; 446 | } 447 | prev_x += w; 448 | } 449 | // shouldn't happen, but if it does, fall through to end-of-line case 450 | } 451 | 452 | // if the last character is a newline, return that. otherwise return 'after' the last character 453 | if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 454 | return i+r.num_chars-1; 455 | else 456 | return i+r.num_chars; 457 | } 458 | 459 | // API click: on mouse down, move the cursor to the clicked location, and reset the selection 460 | static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 461 | { 462 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 463 | // goes off the top or bottom of the text 464 | if( state->single_line ) 465 | { 466 | StbTexteditRow r; 467 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 468 | y = r.ymin; 469 | } 470 | 471 | state->cursor = stb_text_locate_coord(str, x, y); 472 | state->select_start = state->cursor; 473 | state->select_end = state->cursor; 474 | state->has_preferred_x = 0; 475 | } 476 | 477 | // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 478 | static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 479 | { 480 | int p = 0; 481 | 482 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 483 | // goes off the top or bottom of the text 484 | if( state->single_line ) 485 | { 486 | StbTexteditRow r; 487 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 488 | y = r.ymin; 489 | } 490 | 491 | if (state->select_start == state->select_end) 492 | state->select_start = state->cursor; 493 | 494 | p = stb_text_locate_coord(str, x, y); 495 | state->cursor = state->select_end = p; 496 | } 497 | 498 | ///////////////////////////////////////////////////////////////////////////// 499 | // 500 | // Keyboard input handling 501 | // 502 | 503 | // forward declarations 504 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 505 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 506 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 507 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 508 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 509 | 510 | typedef struct 511 | { 512 | float x,y; // position of n'th character 513 | float height; // height of line 514 | int first_char, length; // first char of row, and length 515 | int prev_first; // first char of previous row 516 | } StbFindState; 517 | 518 | // find the x/y location of a character, and remember info about the previous row in 519 | // case we get a move-up event (for page up, we'll have to rescan) 520 | static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) 521 | { 522 | StbTexteditRow r; 523 | int prev_start = 0; 524 | int z = STB_TEXTEDIT_STRINGLEN(str); 525 | int i=0, first; 526 | 527 | if (n == z) { 528 | // if it's at the end, then find the last line -- simpler than trying to 529 | // explicitly handle this case in the regular code 530 | if (single_line) { 531 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 532 | find->y = 0; 533 | find->first_char = 0; 534 | find->length = z; 535 | find->height = r.ymax - r.ymin; 536 | find->x = r.x1; 537 | } else { 538 | find->y = 0; 539 | find->x = 0; 540 | find->height = 1; 541 | while (i < z) { 542 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 543 | prev_start = i; 544 | i += r.num_chars; 545 | } 546 | find->first_char = i; 547 | find->length = 0; 548 | find->prev_first = prev_start; 549 | } 550 | return; 551 | } 552 | 553 | // search rows to find the one that straddles character n 554 | find->y = 0; 555 | 556 | for(;;) { 557 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 558 | if (n < i + r.num_chars) 559 | break; 560 | prev_start = i; 561 | i += r.num_chars; 562 | find->y += r.baseline_y_delta; 563 | } 564 | 565 | find->first_char = first = i; 566 | find->length = r.num_chars; 567 | find->height = r.ymax - r.ymin; 568 | find->prev_first = prev_start; 569 | 570 | // now scan to find xpos 571 | find->x = r.x0; 572 | for (i=0; first+i < n; ++i) 573 | find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 574 | } 575 | 576 | #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 577 | 578 | // make the selection/cursor state valid if client altered the string 579 | static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 580 | { 581 | int n = STB_TEXTEDIT_STRINGLEN(str); 582 | if (STB_TEXT_HAS_SELECTION(state)) { 583 | if (state->select_start > n) state->select_start = n; 584 | if (state->select_end > n) state->select_end = n; 585 | // if clamping forced them to be equal, move the cursor to match 586 | if (state->select_start == state->select_end) 587 | state->cursor = state->select_start; 588 | } 589 | if (state->cursor > n) state->cursor = n; 590 | } 591 | 592 | // delete characters while updating undo 593 | static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 594 | { 595 | stb_text_makeundo_delete(str, state, where, len); 596 | STB_TEXTEDIT_DELETECHARS(str, where, len); 597 | state->has_preferred_x = 0; 598 | } 599 | 600 | // delete the section 601 | static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 602 | { 603 | stb_textedit_clamp(str, state); 604 | if (STB_TEXT_HAS_SELECTION(state)) { 605 | if (state->select_start < state->select_end) { 606 | stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 607 | state->select_end = state->cursor = state->select_start; 608 | } else { 609 | stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 610 | state->select_start = state->cursor = state->select_end; 611 | } 612 | state->has_preferred_x = 0; 613 | } 614 | } 615 | 616 | // canoncialize the selection so start <= end 617 | static void stb_textedit_sortselection(STB_TexteditState *state) 618 | { 619 | if (state->select_end < state->select_start) { 620 | int temp = state->select_end; 621 | state->select_end = state->select_start; 622 | state->select_start = temp; 623 | } 624 | } 625 | 626 | // move cursor to first character of selection 627 | static void stb_textedit_move_to_first(STB_TexteditState *state) 628 | { 629 | if (STB_TEXT_HAS_SELECTION(state)) { 630 | stb_textedit_sortselection(state); 631 | state->cursor = state->select_start; 632 | state->select_end = state->select_start; 633 | state->has_preferred_x = 0; 634 | } 635 | } 636 | 637 | // move cursor to last character of selection 638 | static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 639 | { 640 | if (STB_TEXT_HAS_SELECTION(state)) { 641 | stb_textedit_sortselection(state); 642 | stb_textedit_clamp(str, state); 643 | state->cursor = state->select_end; 644 | state->select_start = state->select_end; 645 | state->has_preferred_x = 0; 646 | } 647 | } 648 | 649 | #ifdef STB_TEXTEDIT_IS_SPACE 650 | static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) 651 | { 652 | return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 653 | } 654 | 655 | #ifndef STB_TEXTEDIT_MOVEWORDLEFT 656 | static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) 657 | { 658 | --c; // always move at least one character 659 | while( c >= 0 && !is_word_boundary( str, c ) ) 660 | --c; 661 | 662 | if( c < 0 ) 663 | c = 0; 664 | 665 | return c; 666 | } 667 | #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 668 | #endif 669 | 670 | #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 671 | static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) 672 | { 673 | const int len = STB_TEXTEDIT_STRINGLEN(str); 674 | ++c; // always move at least one character 675 | while( c < len && !is_word_boundary( str, c ) ) 676 | ++c; 677 | 678 | if( c > len ) 679 | c = len; 680 | 681 | return c; 682 | } 683 | #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 684 | #endif 685 | 686 | #endif 687 | 688 | // update selection and cursor to match each other 689 | static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 690 | { 691 | if (!STB_TEXT_HAS_SELECTION(state)) 692 | state->select_start = state->select_end = state->cursor; 693 | else 694 | state->cursor = state->select_end; 695 | } 696 | 697 | // API cut: delete selection 698 | static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 699 | { 700 | if (STB_TEXT_HAS_SELECTION(state)) { 701 | stb_textedit_delete_selection(str,state); // implicitly clamps 702 | state->has_preferred_x = 0; 703 | return 1; 704 | } 705 | return 0; 706 | } 707 | 708 | // API paste: replace existing selection with passed-in text 709 | static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 710 | { 711 | // if there's a selection, the paste should delete it 712 | stb_textedit_clamp(str, state); 713 | stb_textedit_delete_selection(str,state); 714 | // try to insert the characters 715 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { 716 | stb_text_makeundo_insert(state, state->cursor, len); 717 | state->cursor += len; 718 | state->has_preferred_x = 0; 719 | return 1; 720 | } 721 | // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) 722 | return 0; 723 | } 724 | 725 | #ifndef STB_TEXTEDIT_KEYTYPE 726 | #define STB_TEXTEDIT_KEYTYPE int 727 | #endif 728 | 729 | // API key: process a keyboard input 730 | static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 731 | { 732 | retry: 733 | switch (key) { 734 | default: { 735 | int c = STB_TEXTEDIT_KEYTOTEXT(key); 736 | if (c > 0) { 737 | STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; 738 | 739 | // can't add newline in single-line mode 740 | if (c == '\n' && state->single_line) 741 | break; 742 | 743 | if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 744 | stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 745 | STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 746 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 747 | ++state->cursor; 748 | state->has_preferred_x = 0; 749 | } 750 | } else { 751 | stb_textedit_delete_selection(str,state); // implicitly clamps 752 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 753 | stb_text_makeundo_insert(state, state->cursor, 1); 754 | ++state->cursor; 755 | state->has_preferred_x = 0; 756 | } 757 | } 758 | } 759 | break; 760 | } 761 | 762 | #ifdef STB_TEXTEDIT_K_INSERT 763 | case STB_TEXTEDIT_K_INSERT: 764 | state->insert_mode = !state->insert_mode; 765 | break; 766 | #endif 767 | 768 | case STB_TEXTEDIT_K_UNDO: 769 | stb_text_undo(str, state); 770 | state->has_preferred_x = 0; 771 | break; 772 | 773 | case STB_TEXTEDIT_K_REDO: 774 | stb_text_redo(str, state); 775 | state->has_preferred_x = 0; 776 | break; 777 | 778 | case STB_TEXTEDIT_K_LEFT: 779 | // if currently there's a selection, move cursor to start of selection 780 | if (STB_TEXT_HAS_SELECTION(state)) 781 | stb_textedit_move_to_first(state); 782 | else 783 | if (state->cursor > 0) 784 | --state->cursor; 785 | state->has_preferred_x = 0; 786 | break; 787 | 788 | case STB_TEXTEDIT_K_RIGHT: 789 | // if currently there's a selection, move cursor to end of selection 790 | if (STB_TEXT_HAS_SELECTION(state)) 791 | stb_textedit_move_to_last(str, state); 792 | else 793 | ++state->cursor; 794 | stb_textedit_clamp(str, state); 795 | state->has_preferred_x = 0; 796 | break; 797 | 798 | case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 799 | stb_textedit_clamp(str, state); 800 | stb_textedit_prep_selection_at_cursor(state); 801 | // move selection left 802 | if (state->select_end > 0) 803 | --state->select_end; 804 | state->cursor = state->select_end; 805 | state->has_preferred_x = 0; 806 | break; 807 | 808 | #ifdef STB_TEXTEDIT_MOVEWORDLEFT 809 | case STB_TEXTEDIT_K_WORDLEFT: 810 | if (STB_TEXT_HAS_SELECTION(state)) 811 | stb_textedit_move_to_first(state); 812 | else { 813 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 814 | stb_textedit_clamp( str, state ); 815 | } 816 | break; 817 | 818 | case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 819 | if( !STB_TEXT_HAS_SELECTION( state ) ) 820 | stb_textedit_prep_selection_at_cursor(state); 821 | 822 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 823 | state->select_end = state->cursor; 824 | 825 | stb_textedit_clamp( str, state ); 826 | break; 827 | #endif 828 | 829 | #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 830 | case STB_TEXTEDIT_K_WORDRIGHT: 831 | if (STB_TEXT_HAS_SELECTION(state)) 832 | stb_textedit_move_to_last(str, state); 833 | else { 834 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 835 | stb_textedit_clamp( str, state ); 836 | } 837 | break; 838 | 839 | case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 840 | if( !STB_TEXT_HAS_SELECTION( state ) ) 841 | stb_textedit_prep_selection_at_cursor(state); 842 | 843 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 844 | state->select_end = state->cursor; 845 | 846 | stb_textedit_clamp( str, state ); 847 | break; 848 | #endif 849 | 850 | case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 851 | stb_textedit_prep_selection_at_cursor(state); 852 | // move selection right 853 | ++state->select_end; 854 | stb_textedit_clamp(str, state); 855 | state->cursor = state->select_end; 856 | state->has_preferred_x = 0; 857 | break; 858 | 859 | case STB_TEXTEDIT_K_DOWN: 860 | case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: 861 | case STB_TEXTEDIT_K_PGDOWN: 862 | case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { 863 | StbFindState find; 864 | StbTexteditRow row; 865 | int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 866 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; 867 | int row_count = is_page ? state->row_count_per_page : 1; 868 | 869 | if (!is_page && state->single_line) { 870 | // on windows, up&down in single-line behave like left&right 871 | key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 872 | goto retry; 873 | } 874 | 875 | if (sel) 876 | stb_textedit_prep_selection_at_cursor(state); 877 | else if (STB_TEXT_HAS_SELECTION(state)) 878 | stb_textedit_move_to_last(str, state); 879 | 880 | // compute current position of cursor point 881 | stb_textedit_clamp(str, state); 882 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 883 | 884 | for (j = 0; j < row_count; ++j) { 885 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 886 | int start = find.first_char + find.length; 887 | 888 | if (find.length == 0) 889 | break; 890 | 891 | // [DEAR IMGUI] 892 | // going down while being on the last line shouldn't bring us to that line end 893 | if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) 894 | break; 895 | 896 | // now find character position down a row 897 | state->cursor = start; 898 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 899 | x = row.x0; 900 | for (i=0; i < row.num_chars; ++i) { 901 | float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 902 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 903 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 904 | break; 905 | #endif 906 | x += dx; 907 | if (x > goal_x) 908 | break; 909 | ++state->cursor; 910 | } 911 | stb_textedit_clamp(str, state); 912 | 913 | state->has_preferred_x = 1; 914 | state->preferred_x = goal_x; 915 | 916 | if (sel) 917 | state->select_end = state->cursor; 918 | 919 | // go to next line 920 | find.first_char = find.first_char + find.length; 921 | find.length = row.num_chars; 922 | } 923 | break; 924 | } 925 | 926 | case STB_TEXTEDIT_K_UP: 927 | case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: 928 | case STB_TEXTEDIT_K_PGUP: 929 | case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { 930 | StbFindState find; 931 | StbTexteditRow row; 932 | int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 933 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; 934 | int row_count = is_page ? state->row_count_per_page : 1; 935 | 936 | if (!is_page && state->single_line) { 937 | // on windows, up&down become left&right 938 | key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 939 | goto retry; 940 | } 941 | 942 | if (sel) 943 | stb_textedit_prep_selection_at_cursor(state); 944 | else if (STB_TEXT_HAS_SELECTION(state)) 945 | stb_textedit_move_to_first(state); 946 | 947 | // compute current position of cursor point 948 | stb_textedit_clamp(str, state); 949 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 950 | 951 | for (j = 0; j < row_count; ++j) { 952 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 953 | 954 | // can only go up if there's a previous row 955 | if (find.prev_first == find.first_char) 956 | break; 957 | 958 | // now find character position up a row 959 | state->cursor = find.prev_first; 960 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 961 | x = row.x0; 962 | for (i=0; i < row.num_chars; ++i) { 963 | float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 964 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 965 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 966 | break; 967 | #endif 968 | x += dx; 969 | if (x > goal_x) 970 | break; 971 | ++state->cursor; 972 | } 973 | stb_textedit_clamp(str, state); 974 | 975 | state->has_preferred_x = 1; 976 | state->preferred_x = goal_x; 977 | 978 | if (sel) 979 | state->select_end = state->cursor; 980 | 981 | // go to previous line 982 | // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) 983 | prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; 984 | while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) 985 | --prev_scan; 986 | find.first_char = find.prev_first; 987 | find.prev_first = prev_scan; 988 | } 989 | break; 990 | } 991 | 992 | case STB_TEXTEDIT_K_DELETE: 993 | case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 994 | if (STB_TEXT_HAS_SELECTION(state)) 995 | stb_textedit_delete_selection(str, state); 996 | else { 997 | int n = STB_TEXTEDIT_STRINGLEN(str); 998 | if (state->cursor < n) 999 | stb_textedit_delete(str, state, state->cursor, 1); 1000 | } 1001 | state->has_preferred_x = 0; 1002 | break; 1003 | 1004 | case STB_TEXTEDIT_K_BACKSPACE: 1005 | case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 1006 | if (STB_TEXT_HAS_SELECTION(state)) 1007 | stb_textedit_delete_selection(str, state); 1008 | else { 1009 | stb_textedit_clamp(str, state); 1010 | if (state->cursor > 0) { 1011 | stb_textedit_delete(str, state, state->cursor-1, 1); 1012 | --state->cursor; 1013 | } 1014 | } 1015 | state->has_preferred_x = 0; 1016 | break; 1017 | 1018 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1019 | case STB_TEXTEDIT_K_TEXTSTART2: 1020 | #endif 1021 | case STB_TEXTEDIT_K_TEXTSTART: 1022 | state->cursor = state->select_start = state->select_end = 0; 1023 | state->has_preferred_x = 0; 1024 | break; 1025 | 1026 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1027 | case STB_TEXTEDIT_K_TEXTEND2: 1028 | #endif 1029 | case STB_TEXTEDIT_K_TEXTEND: 1030 | state->cursor = STB_TEXTEDIT_STRINGLEN(str); 1031 | state->select_start = state->select_end = 0; 1032 | state->has_preferred_x = 0; 1033 | break; 1034 | 1035 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1036 | case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 1037 | #endif 1038 | case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 1039 | stb_textedit_prep_selection_at_cursor(state); 1040 | state->cursor = state->select_end = 0; 1041 | state->has_preferred_x = 0; 1042 | break; 1043 | 1044 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1045 | case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 1046 | #endif 1047 | case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 1048 | stb_textedit_prep_selection_at_cursor(state); 1049 | state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 1050 | state->has_preferred_x = 0; 1051 | break; 1052 | 1053 | 1054 | #ifdef STB_TEXTEDIT_K_LINESTART2 1055 | case STB_TEXTEDIT_K_LINESTART2: 1056 | #endif 1057 | case STB_TEXTEDIT_K_LINESTART: 1058 | stb_textedit_clamp(str, state); 1059 | stb_textedit_move_to_first(state); 1060 | if (state->single_line) 1061 | state->cursor = 0; 1062 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1063 | --state->cursor; 1064 | state->has_preferred_x = 0; 1065 | break; 1066 | 1067 | #ifdef STB_TEXTEDIT_K_LINEEND2 1068 | case STB_TEXTEDIT_K_LINEEND2: 1069 | #endif 1070 | case STB_TEXTEDIT_K_LINEEND: { 1071 | int n = STB_TEXTEDIT_STRINGLEN(str); 1072 | stb_textedit_clamp(str, state); 1073 | stb_textedit_move_to_first(state); 1074 | if (state->single_line) 1075 | state->cursor = n; 1076 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1077 | ++state->cursor; 1078 | state->has_preferred_x = 0; 1079 | break; 1080 | } 1081 | 1082 | #ifdef STB_TEXTEDIT_K_LINESTART2 1083 | case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1084 | #endif 1085 | case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1086 | stb_textedit_clamp(str, state); 1087 | stb_textedit_prep_selection_at_cursor(state); 1088 | if (state->single_line) 1089 | state->cursor = 0; 1090 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1091 | --state->cursor; 1092 | state->select_end = state->cursor; 1093 | state->has_preferred_x = 0; 1094 | break; 1095 | 1096 | #ifdef STB_TEXTEDIT_K_LINEEND2 1097 | case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1098 | #endif 1099 | case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1100 | int n = STB_TEXTEDIT_STRINGLEN(str); 1101 | stb_textedit_clamp(str, state); 1102 | stb_textedit_prep_selection_at_cursor(state); 1103 | if (state->single_line) 1104 | state->cursor = n; 1105 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1106 | ++state->cursor; 1107 | state->select_end = state->cursor; 1108 | state->has_preferred_x = 0; 1109 | break; 1110 | } 1111 | } 1112 | } 1113 | 1114 | ///////////////////////////////////////////////////////////////////////////// 1115 | // 1116 | // Undo processing 1117 | // 1118 | // @OPTIMIZE: the undo/redo buffer should be circular 1119 | 1120 | static void stb_textedit_flush_redo(StbUndoState *state) 1121 | { 1122 | state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1123 | state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1124 | } 1125 | 1126 | // discard the oldest entry in the undo list 1127 | static void stb_textedit_discard_undo(StbUndoState *state) 1128 | { 1129 | if (state->undo_point > 0) { 1130 | // if the 0th undo state has characters, clean those up 1131 | if (state->undo_rec[0].char_storage >= 0) { 1132 | int n = state->undo_rec[0].insert_length, i; 1133 | // delete n characters from all other records 1134 | state->undo_char_point -= n; 1135 | STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); 1136 | for (i=0; i < state->undo_point; ++i) 1137 | if (state->undo_rec[i].char_storage >= 0) 1138 | state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 1139 | } 1140 | --state->undo_point; 1141 | STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 1142 | } 1143 | } 1144 | 1145 | // discard the oldest entry in the redo list--it's bad if this 1146 | // ever happens, but because undo & redo have to store the actual 1147 | // characters in different cases, the redo character buffer can 1148 | // fill up even though the undo buffer didn't 1149 | static void stb_textedit_discard_redo(StbUndoState *state) 1150 | { 1151 | int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; 1152 | 1153 | if (state->redo_point <= k) { 1154 | // if the k'th undo state has characters, clean those up 1155 | if (state->undo_rec[k].char_storage >= 0) { 1156 | int n = state->undo_rec[k].insert_length, i; 1157 | // move the remaining redo character data to the end of the buffer 1158 | state->redo_char_point += n; 1159 | STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); 1160 | // adjust the position of all the other records to account for above memmove 1161 | for (i=state->redo_point; i < k; ++i) 1162 | if (state->undo_rec[i].char_storage >= 0) 1163 | state->undo_rec[i].char_storage += n; 1164 | } 1165 | // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 1166 | // [DEAR IMGUI] 1167 | size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 1168 | const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 1169 | const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 1170 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 1171 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 1172 | STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 1173 | 1174 | // now move redo_point to point to the new one 1175 | ++state->redo_point; 1176 | } 1177 | } 1178 | 1179 | static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1180 | { 1181 | // any time we create a new undo record, we discard redo 1182 | stb_textedit_flush_redo(state); 1183 | 1184 | // if we have no free records, we have to make room, by sliding the 1185 | // existing records down 1186 | if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1187 | stb_textedit_discard_undo(state); 1188 | 1189 | // if the characters to store won't possibly fit in the buffer, we can't undo 1190 | if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { 1191 | state->undo_point = 0; 1192 | state->undo_char_point = 0; 1193 | return NULL; 1194 | } 1195 | 1196 | // if we don't have enough free characters in the buffer, we have to make room 1197 | while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) 1198 | stb_textedit_discard_undo(state); 1199 | 1200 | return &state->undo_rec[state->undo_point++]; 1201 | } 1202 | 1203 | static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1204 | { 1205 | StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1206 | if (r == NULL) 1207 | return NULL; 1208 | 1209 | r->where = pos; 1210 | r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; 1211 | r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; 1212 | 1213 | if (insert_len == 0) { 1214 | r->char_storage = -1; 1215 | return NULL; 1216 | } else { 1217 | r->char_storage = state->undo_char_point; 1218 | state->undo_char_point += insert_len; 1219 | return &state->undo_char[r->char_storage]; 1220 | } 1221 | } 1222 | 1223 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1224 | { 1225 | StbUndoState *s = &state->undostate; 1226 | StbUndoRecord u, *r; 1227 | if (s->undo_point == 0) 1228 | return; 1229 | 1230 | // we need to do two things: apply the undo record, and create a redo record 1231 | u = s->undo_rec[s->undo_point-1]; 1232 | r = &s->undo_rec[s->redo_point-1]; 1233 | r->char_storage = -1; 1234 | 1235 | r->insert_length = u.delete_length; 1236 | r->delete_length = u.insert_length; 1237 | r->where = u.where; 1238 | 1239 | if (u.delete_length) { 1240 | // if the undo record says to delete characters, then the redo record will 1241 | // need to re-insert the characters that get deleted, so we need to store 1242 | // them. 1243 | 1244 | // there are three cases: 1245 | // there's enough room to store the characters 1246 | // characters stored for *redoing* don't leave room for redo 1247 | // characters stored for *undoing* don't leave room for redo 1248 | // if the last is true, we have to bail 1249 | 1250 | if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { 1251 | // the undo records take up too much character space; there's no space to store the redo characters 1252 | r->insert_length = 0; 1253 | } else { 1254 | int i; 1255 | 1256 | // there's definitely room to store the characters eventually 1257 | while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1258 | // should never happen: 1259 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1260 | return; 1261 | // there's currently not enough room, so discard a redo record 1262 | stb_textedit_discard_redo(s); 1263 | } 1264 | r = &s->undo_rec[s->redo_point-1]; 1265 | 1266 | r->char_storage = s->redo_char_point - u.delete_length; 1267 | s->redo_char_point = s->redo_char_point - u.delete_length; 1268 | 1269 | // now save the characters 1270 | for (i=0; i < u.delete_length; ++i) 1271 | s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1272 | } 1273 | 1274 | // now we can carry out the deletion 1275 | STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1276 | } 1277 | 1278 | // check type of recorded action: 1279 | if (u.insert_length) { 1280 | // easy case: was a deletion, so we need to insert n characters 1281 | STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1282 | s->undo_char_point -= u.insert_length; 1283 | } 1284 | 1285 | state->cursor = u.where + u.insert_length; 1286 | 1287 | s->undo_point--; 1288 | s->redo_point--; 1289 | } 1290 | 1291 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1292 | { 1293 | StbUndoState *s = &state->undostate; 1294 | StbUndoRecord *u, r; 1295 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1296 | return; 1297 | 1298 | // we need to do two things: apply the redo record, and create an undo record 1299 | u = &s->undo_rec[s->undo_point]; 1300 | r = s->undo_rec[s->redo_point]; 1301 | 1302 | // we KNOW there must be room for the undo record, because the redo record 1303 | // was derived from an undo record 1304 | 1305 | u->delete_length = r.insert_length; 1306 | u->insert_length = r.delete_length; 1307 | u->where = r.where; 1308 | u->char_storage = -1; 1309 | 1310 | if (r.delete_length) { 1311 | // the redo record requires us to delete characters, so the undo record 1312 | // needs to store the characters 1313 | 1314 | if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1315 | u->insert_length = 0; 1316 | u->delete_length = 0; 1317 | } else { 1318 | int i; 1319 | u->char_storage = s->undo_char_point; 1320 | s->undo_char_point = s->undo_char_point + u->insert_length; 1321 | 1322 | // now save the characters 1323 | for (i=0; i < u->insert_length; ++i) 1324 | s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1325 | } 1326 | 1327 | STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1328 | } 1329 | 1330 | if (r.insert_length) { 1331 | // easy case: need to insert n characters 1332 | STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1333 | s->redo_char_point += r.insert_length; 1334 | } 1335 | 1336 | state->cursor = r.where + r.insert_length; 1337 | 1338 | s->undo_point++; 1339 | s->redo_point++; 1340 | } 1341 | 1342 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1343 | { 1344 | stb_text_createundo(&state->undostate, where, 0, length); 1345 | } 1346 | 1347 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1348 | { 1349 | int i; 1350 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1351 | if (p) { 1352 | for (i=0; i < length; ++i) 1353 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1354 | } 1355 | } 1356 | 1357 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1358 | { 1359 | int i; 1360 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1361 | if (p) { 1362 | for (i=0; i < old_length; ++i) 1363 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1364 | } 1365 | } 1366 | 1367 | // reset the state to default 1368 | static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1369 | { 1370 | state->undostate.undo_point = 0; 1371 | state->undostate.undo_char_point = 0; 1372 | state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1373 | state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1374 | state->select_end = state->select_start = 0; 1375 | state->cursor = 0; 1376 | state->has_preferred_x = 0; 1377 | state->preferred_x = 0; 1378 | state->cursor_at_end_of_line = 0; 1379 | state->initialized = 1; 1380 | state->single_line = (unsigned char) is_single_line; 1381 | state->insert_mode = 0; 1382 | state->row_count_per_page = 0; 1383 | } 1384 | 1385 | // API initialize 1386 | static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1387 | { 1388 | stb_textedit_clear_state(state, is_single_line); 1389 | } 1390 | 1391 | #if defined(__GNUC__) || defined(__clang__) 1392 | #pragma GCC diagnostic push 1393 | #pragma GCC diagnostic ignored "-Wcast-qual" 1394 | #endif 1395 | 1396 | static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) 1397 | { 1398 | return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); 1399 | } 1400 | 1401 | #if defined(__GNUC__) || defined(__clang__) 1402 | #pragma GCC diagnostic pop 1403 | #endif 1404 | 1405 | #endif//STB_TEXTEDIT_IMPLEMENTATION 1406 | 1407 | /* 1408 | ------------------------------------------------------------------------------ 1409 | This software is available under 2 licenses -- choose whichever you prefer. 1410 | ------------------------------------------------------------------------------ 1411 | ALTERNATIVE A - MIT License 1412 | Copyright (c) 2017 Sean Barrett 1413 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1414 | this software and associated documentation files (the "Software"), to deal in 1415 | the Software without restriction, including without limitation the rights to 1416 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1417 | of the Software, and to permit persons to whom the Software is furnished to do 1418 | so, subject to the following conditions: 1419 | The above copyright notice and this permission notice shall be included in all 1420 | copies or substantial portions of the Software. 1421 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1422 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1423 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1424 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1425 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1426 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1427 | SOFTWARE. 1428 | ------------------------------------------------------------------------------ 1429 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1430 | This is free and unencumbered software released into the public domain. 1431 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1432 | software, either in source code form or as a compiled binary, for any purpose, 1433 | commercial or non-commercial, and by any means. 1434 | In jurisdictions that recognize copyright laws, the author or authors of this 1435 | software dedicate any and all copyright interest in the software to the public 1436 | domain. We make this dedication for the benefit of the public at large and to 1437 | the detriment of our heirs and successors. We intend this dedication to be an 1438 | overt act of relinquishment in perpetuity of all present and future rights to 1439 | this software under copyright law. 1440 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1441 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1442 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1443 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1444 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1445 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1446 | ------------------------------------------------------------------------------ 1447 | */ 1448 | -------------------------------------------------------------------------------- /imgui/imguiquick.cmakeinc: -------------------------------------------------------------------------------- 1 | target_sources(${imgui_target} PRIVATE 2 | ${imgui_base}/qrhiimguiitem.cpp 3 | ${imgui_base}/qrhiimguiitem.h 4 | ) 5 | -------------------------------------------------------------------------------- /imgui/qrhiimgui.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include "qrhiimgui.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "imgui.h" 12 | 13 | // the imgui default 14 | static_assert(sizeof(ImDrawVert) == 20); 15 | // switched to uint in imconfig.h to avoid trouble with 4 byte offset alignment reqs 16 | static_assert(sizeof(ImDrawIdx) == 4); 17 | 18 | QT_BEGIN_NAMESPACE 19 | 20 | static QShader getShader(const QString &name) 21 | { 22 | QFile f(name); 23 | if (f.open(QIODevice::ReadOnly)) 24 | return QShader::fromSerialized(f.readAll()); 25 | 26 | return QShader(); 27 | } 28 | 29 | QRhiImguiRenderer::~QRhiImguiRenderer() 30 | { 31 | releaseResources(); 32 | } 33 | 34 | void QRhiImguiRenderer::releaseResources() 35 | { 36 | for (Texture &t : m_textures) { 37 | if (t.ownTex) 38 | delete t.tex; 39 | delete t.srb; 40 | } 41 | m_textures.clear(); 42 | 43 | m_vbuf.reset(); 44 | m_ibuf.reset(); 45 | m_ubuf.reset(); 46 | m_ps.reset(); 47 | m_linearSampler.reset(); 48 | m_nearestSampler.reset(); 49 | 50 | m_rhi = nullptr; 51 | } 52 | 53 | void QRhiImguiRenderer::prepare(QRhi *rhi, 54 | QRhiRenderTarget *rt, 55 | QRhiCommandBuffer *cb, 56 | const QMatrix4x4 &mvp, 57 | float opacity, 58 | float hdrWhiteLevelMultiplierOrZeroForSDRsRGB) 59 | { 60 | if (!m_rhi) { 61 | m_rhi = rhi; 62 | } else if (m_rhi != rhi) { 63 | releaseResources(); 64 | m_rhi = rhi; 65 | } 66 | 67 | if (!m_rhi || f.draw.isEmpty()) 68 | return; 69 | 70 | m_rt = rt; 71 | m_cb = cb; 72 | 73 | if (!m_vbuf) { 74 | m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, f.totalVbufSize)); 75 | m_vbuf->setName(QByteArrayLiteral("imgui vertex buffer")); 76 | if (!m_vbuf->create()) 77 | return; 78 | } else { 79 | if (f.totalVbufSize > m_vbuf->size()) { 80 | m_vbuf->setSize(f.totalVbufSize); 81 | if (!m_vbuf->create()) 82 | return; 83 | } 84 | } 85 | if (!m_ibuf) { 86 | m_ibuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::IndexBuffer, f.totalIbufSize)); 87 | m_ibuf->setName(QByteArrayLiteral("imgui index buffer")); 88 | if (!m_ibuf->create()) 89 | return; 90 | } else { 91 | if (f.totalIbufSize > m_ibuf->size()) { 92 | m_ibuf->setSize(f.totalIbufSize); 93 | if (!m_ibuf->create()) 94 | return; 95 | } 96 | } 97 | 98 | if (!m_ubuf) { 99 | m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4 + 4)); 100 | m_ubuf->setName(QByteArrayLiteral("imgui uniform buffer")); 101 | if (!m_ubuf->create()) 102 | return; 103 | } 104 | 105 | if (!m_linearSampler) { 106 | m_linearSampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, 107 | QRhiSampler::Repeat, QRhiSampler::Repeat)); 108 | m_linearSampler->setName(QByteArrayLiteral("imgui linear sampler")); 109 | if (!m_linearSampler->create()) 110 | return; 111 | } 112 | 113 | if (!m_nearestSampler) { 114 | m_nearestSampler.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, 115 | QRhiSampler::Repeat, QRhiSampler::Repeat)); 116 | m_nearestSampler->setName(QByteArrayLiteral("imgui nearest sampler")); 117 | if (!m_nearestSampler->create()) 118 | return; 119 | } 120 | 121 | if (m_textures.isEmpty()) { 122 | Texture fontTex; 123 | fontTex.image = sf.fontTextureData; 124 | m_textures.insert(nullptr, fontTex); 125 | sf.reset(); 126 | } else if (sf.isValid()) { 127 | Texture fontTex; 128 | fontTex.image = sf.fontTextureData; 129 | Texture &fontTexEntry(m_textures[nullptr]); 130 | delete fontTexEntry.tex; 131 | delete fontTexEntry.srb; 132 | fontTexEntry = fontTex; 133 | sf.reset(); 134 | } 135 | 136 | QVarLengthArray texturesNeedUpdate; 137 | for (auto it = m_textures.begin(), end = m_textures.end(); it != end; ++it) { 138 | Texture &t(*it); 139 | if (!t.tex) { 140 | t.tex = m_rhi->newTexture(QRhiTexture::RGBA8, t.image.size()); 141 | t.tex->setName(QByteArrayLiteral("imgui texture ") + QByteArray::number(qintptr(it.key()))); 142 | if (!t.tex->create()) 143 | return; 144 | texturesNeedUpdate.append(it.key()); 145 | } 146 | if (!t.srb) { 147 | QRhiSampler *sampler = t.filter == QRhiSampler::Nearest ? m_nearestSampler.get() : m_linearSampler.get(); 148 | t.srb = m_rhi->newShaderResourceBindings(); 149 | t.srb->setBindings({ 150 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf.get()), 151 | QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, t.tex, sampler) 152 | }); 153 | if (!t.srb->create()) 154 | return; 155 | } 156 | } 157 | 158 | // If layer.enabled is toggled on the item or an ancestor, the render 159 | // target is then suddenly different and may not be compatible. 160 | if (m_ps && m_rt->renderPassDescriptor()->serializedFormat() != m_renderPassFormat) 161 | m_ps.reset(); 162 | 163 | if (m_ps && m_rt->sampleCount() != m_ps->sampleCount()) 164 | m_ps.reset(); 165 | 166 | if (!m_ps) { 167 | QShader vs = getShader(QLatin1String(":/imgui.vert.qsb")); 168 | QShader fs = getShader(QLatin1String(":/imgui.frag.qsb")); 169 | if (!vs.isValid() || !fs.isValid()) { 170 | qWarning("Failed to load imgui shaders"); 171 | return; 172 | } 173 | 174 | m_ps.reset(m_rhi->newGraphicsPipeline()); 175 | QRhiGraphicsPipeline::TargetBlend blend; 176 | blend.enable = true; 177 | // Premultiplied alpha (matches imgui.frag). Would not be needed if we 178 | // only cared about outputting to the window (the common case), but 179 | // once going through a texture (Item layer, ShaderEffect) which is 180 | // then sampled by Quick, the result wouldn't be correct otherwise. 181 | blend.srcColor = QRhiGraphicsPipeline::One; 182 | blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha; 183 | blend.srcAlpha = QRhiGraphicsPipeline::One; 184 | blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha; 185 | m_ps->setTargetBlends({ blend }); 186 | m_ps->setCullMode(QRhiGraphicsPipeline::None); 187 | m_ps->setDepthTest(true); 188 | m_ps->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); 189 | m_ps->setDepthWrite(false); 190 | m_ps->setFlags(QRhiGraphicsPipeline::UsesScissor); 191 | 192 | m_ps->setShaderStages({ 193 | { QRhiShaderStage::Vertex, vs }, 194 | { QRhiShaderStage::Fragment, fs } 195 | }); 196 | 197 | QRhiVertexInputLayout inputLayout; 198 | inputLayout.setBindings({ 199 | { 4 * sizeof(float) + sizeof(quint32) } 200 | }); 201 | inputLayout.setAttributes({ 202 | { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, 203 | { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }, 204 | { 0, 2, QRhiVertexInputAttribute::UNormByte4, 4 * sizeof(float) } 205 | }); 206 | m_ps->setVertexInputLayout(inputLayout); 207 | m_ps->setSampleCount(rt->sampleCount()); 208 | m_ps->setShaderResourceBindings(m_textures[0].srb); 209 | m_ps->setRenderPassDescriptor(m_rt->renderPassDescriptor()); 210 | m_renderPassFormat = m_rt->renderPassDescriptor()->serializedFormat(); 211 | 212 | if (!m_ps->create()) 213 | return; 214 | } 215 | 216 | QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch(); 217 | 218 | for (const CmdListBuffer &b : f.vbuf) 219 | u->updateDynamicBuffer(m_vbuf.get(), b.offset, b.data.size(), b.data.constData()); 220 | 221 | for (const CmdListBuffer &b : f.ibuf) 222 | u->updateDynamicBuffer(m_ibuf.get(), b.offset, b.data.size(), b.data.constData()); 223 | 224 | u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData()); 225 | u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &opacity); 226 | u->updateDynamicBuffer(m_ubuf.get(), 68, 4, &hdrWhiteLevelMultiplierOrZeroForSDRsRGB); 227 | 228 | for (int i = 0; i < texturesNeedUpdate.count(); ++i) { 229 | Texture &t(m_textures[texturesNeedUpdate[i]]); 230 | if (!t.image.isNull()) { 231 | u->uploadTexture(t.tex, t.image); 232 | t.image = QImage(); 233 | } 234 | } 235 | 236 | m_cb->resourceUpdate(u); 237 | } 238 | 239 | void QRhiImguiRenderer::render() 240 | { 241 | if (!m_rhi || f.draw.isEmpty() || !m_ps) 242 | return; 243 | 244 | m_cb->setGraphicsPipeline(m_ps.get()); 245 | 246 | const QSize viewportSize = m_rt->pixelSize(); 247 | bool needsViewport = true; 248 | 249 | for (const DrawCmd &c : f.draw) { 250 | QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), f.vbuf[c.cmdListBufferIdx].offset); 251 | if (needsViewport) { 252 | needsViewport = false; 253 | m_cb->setViewport({ 0, 0, float(viewportSize.width()), float(viewportSize.height()) }); 254 | } 255 | const float sx1 = c.clipRect.x() + c.itemPixelOffset.x(); 256 | const float sy1 = c.clipRect.y() + c.itemPixelOffset.y(); 257 | const float sx2 = c.clipRect.z() + c.itemPixelOffset.x(); 258 | const float sy2 = c.clipRect.w() + c.itemPixelOffset.y(); 259 | QPoint scissorPos = QPointF(sx1, viewportSize.height() - sy2).toPoint(); 260 | QSize scissorSize = QSizeF(sx2 - sx1, sy2 - sy1).toSize(); 261 | scissorPos.setX(qMax(0, scissorPos.x())); 262 | scissorPos.setY(qMax(0, scissorPos.y())); 263 | scissorSize.setWidth(qMin(viewportSize.width(), scissorSize.width())); 264 | scissorSize.setHeight(qMin(viewportSize.height(), scissorSize.height())); 265 | m_cb->setScissor({ scissorPos.x(), scissorPos.y(), scissorSize.width(), scissorSize.height() }); 266 | m_cb->setShaderResources(m_textures[c.textureId].srb); 267 | m_cb->setVertexInput(0, 1, &vbufBinding, m_ibuf.get(), c.indexOffset, QRhiCommandBuffer::IndexUInt32); 268 | m_cb->drawIndexed(c.elemCount); 269 | } 270 | } 271 | 272 | void QRhiImguiRenderer::registerCustomTexture(void *id, 273 | QRhiTexture *texture, 274 | QRhiSampler::Filter filter, 275 | CustomTextureOwnership ownership) 276 | { 277 | Q_ASSERT(id); 278 | auto it = m_textures.constFind(id); 279 | if (it != m_textures.cend()) { 280 | if (it->ownTex) 281 | delete it->tex; 282 | delete it->srb; 283 | } 284 | Texture t; 285 | t.tex = texture; 286 | t.filter = filter; 287 | t.ownTex = ownership == TakeCustomTextureOwnership; 288 | m_textures[id] = t; 289 | } 290 | 291 | static const char *getClipboardText(void *) 292 | { 293 | static QByteArray contents; 294 | contents = QGuiApplication::clipboard()->text().toUtf8(); 295 | return contents.constData(); 296 | } 297 | 298 | static void setClipboardText(void *, const char *text) 299 | { 300 | QGuiApplication::clipboard()->setText(QString::fromUtf8(text)); 301 | } 302 | 303 | QRhiImgui::QRhiImgui() 304 | { 305 | context = ImGui::CreateContext(); 306 | ImGui::SetCurrentContext(static_cast(context)); 307 | rebuildFontAtlas(); 308 | ImGuiIO &io(ImGui::GetIO()); 309 | io.GetClipboardTextFn = getClipboardText; 310 | io.SetClipboardTextFn = setClipboardText; 311 | } 312 | 313 | QRhiImgui::~QRhiImgui() 314 | { 315 | ImGui::DestroyContext(static_cast(context)); 316 | } 317 | 318 | void QRhiImgui::rebuildFontAtlas() 319 | { 320 | ImGui::SetCurrentContext(static_cast(context)); 321 | ImGuiIO &io(ImGui::GetIO()); 322 | unsigned char *pixels; 323 | int w, h; 324 | io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h); 325 | const QImage wrapperImg(const_cast(pixels), w, h, QImage::Format_RGBA8888); 326 | sf.fontTextureData = wrapperImg.copy(); 327 | io.Fonts->SetTexID(nullptr); 328 | } 329 | 330 | void QRhiImgui::rebuildFontAtlasWithFont(const QString &filename) 331 | { 332 | QFile f(filename); 333 | if (!f.open(QIODevice::ReadOnly)) { 334 | qWarning("Failed to open %s", qPrintable(filename)); 335 | return; 336 | } 337 | QByteArray font = f.readAll(); 338 | ImGui::SetCurrentContext(static_cast(context)); 339 | ImFontConfig fontCfg; 340 | fontCfg.FontDataOwnedByAtlas = false; 341 | ImGui::GetIO().Fonts->Clear(); 342 | ImGui::GetIO().Fonts->AddFontFromMemoryTTF(font.data(), font.size(), 20.0f, &fontCfg); 343 | rebuildFontAtlas(); 344 | } 345 | 346 | void QRhiImgui::nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPointF &logicalOffset, FrameFunc frameFunc) 347 | { 348 | ImGui::SetCurrentContext(static_cast(context)); 349 | ImGuiIO &io(ImGui::GetIO()); 350 | 351 | const QPointF itemPixelOffset = logicalOffset * dpr; 352 | f.outputPixelSize = (logicalOutputSize * dpr).toSize(); 353 | io.DisplaySize.x = logicalOutputSize.width(); 354 | io.DisplaySize.y = logicalOutputSize.height(); 355 | io.DisplayFramebufferScale = ImVec2(dpr, dpr); 356 | 357 | ImGui::NewFrame(); 358 | if (frameFunc) 359 | frameFunc(); 360 | ImGui::Render(); 361 | 362 | ImDrawData *draw = ImGui::GetDrawData(); 363 | draw->ScaleClipRects(ImVec2(dpr, dpr)); 364 | 365 | f.vbuf.resize(draw->CmdListsCount); 366 | f.ibuf.resize(draw->CmdListsCount); 367 | f.totalVbufSize = 0; 368 | f.totalIbufSize = 0; 369 | for (int n = 0; n < draw->CmdListsCount; ++n) { 370 | const ImDrawList *cmdList = draw->CmdLists[n]; 371 | const int vbufSize = cmdList->VtxBuffer.Size * sizeof(ImDrawVert); 372 | f.vbuf[n].offset = f.totalVbufSize; 373 | f.totalVbufSize += vbufSize; 374 | const int ibufSize = cmdList->IdxBuffer.Size * sizeof(ImDrawIdx); 375 | f.ibuf[n].offset = f.totalIbufSize; 376 | f.totalIbufSize += ibufSize; 377 | } 378 | f.draw.clear(); 379 | for (int n = 0; n < draw->CmdListsCount; ++n) { 380 | const ImDrawList *cmdList = draw->CmdLists[n]; 381 | f.vbuf[n].data = QByteArray(reinterpret_cast(cmdList->VtxBuffer.Data), 382 | cmdList->VtxBuffer.Size * sizeof(ImDrawVert)); 383 | f.ibuf[n].data = QByteArray(reinterpret_cast(cmdList->IdxBuffer.Data), 384 | cmdList->IdxBuffer.Size * sizeof(ImDrawIdx)); 385 | const ImDrawIdx *indexBufOffset = nullptr; 386 | for (int i = 0; i < cmdList->CmdBuffer.Size; ++i) { 387 | const ImDrawCmd *cmd = &cmdList->CmdBuffer[i]; 388 | const quint32 indexOffset = f.ibuf[n].offset + quintptr(indexBufOffset); 389 | if (!cmd->UserCallback) { 390 | QRhiImguiRenderer::DrawCmd dc; 391 | dc.cmdListBufferIdx = n; 392 | dc.textureId = cmd->TextureId; 393 | dc.indexOffset = indexOffset; 394 | dc.elemCount = cmd->ElemCount; 395 | dc.itemPixelOffset = itemPixelOffset; 396 | dc.clipRect = QVector4D(cmd->ClipRect.x, cmd->ClipRect.y, cmd->ClipRect.z, cmd->ClipRect.w); 397 | f.draw.append(dc); 398 | } else { 399 | cmd->UserCallback(cmdList, cmd); 400 | } 401 | indexBufOffset += cmd->ElemCount; 402 | } 403 | } 404 | } 405 | 406 | void QRhiImgui::syncRenderer(QRhiImguiRenderer *renderer) 407 | { 408 | if (sf.isValid()) { 409 | renderer->sf = sf; 410 | sf.reset(); 411 | } 412 | renderer->f = std::move(f); 413 | } 414 | 415 | static void updateKeyboardModifiers(Qt::KeyboardModifiers modifiers) 416 | { 417 | ImGuiIO &io(ImGui::GetIO()); 418 | io.AddKeyEvent(ImGuiKey_ModCtrl, modifiers.testFlag(Qt::ControlModifier)); 419 | io.AddKeyEvent(ImGuiKey_ModShift, modifiers.testFlag(Qt::ShiftModifier)); 420 | io.AddKeyEvent(ImGuiKey_ModAlt, modifiers.testFlag(Qt::AltModifier)); 421 | io.AddKeyEvent(ImGuiKey_ModSuper, modifiers.testFlag(Qt::MetaModifier)); 422 | } 423 | 424 | static ImGuiKey mapKey(int k) 425 | { 426 | switch (k) { 427 | case Qt::Key_Space: 428 | return ImGuiKey_Space; 429 | case Qt::Key_Apostrophe: 430 | return ImGuiKey_Apostrophe; 431 | case Qt::Key_Comma: 432 | return ImGuiKey_Comma; 433 | case Qt::Key_Minus: 434 | return ImGuiKey_Minus; 435 | case Qt::Key_Period: 436 | return ImGuiKey_Period; 437 | case Qt::Key_Slash: 438 | return ImGuiKey_Slash; 439 | case Qt::Key_0: 440 | return ImGuiKey_0; 441 | case Qt::Key_1: 442 | return ImGuiKey_1; 443 | case Qt::Key_2: 444 | return ImGuiKey_2; 445 | case Qt::Key_3: 446 | return ImGuiKey_3; 447 | case Qt::Key_4: 448 | return ImGuiKey_4; 449 | case Qt::Key_5: 450 | return ImGuiKey_5; 451 | case Qt::Key_6: 452 | return ImGuiKey_6; 453 | case Qt::Key_7: 454 | return ImGuiKey_8; 455 | case Qt::Key_8: 456 | return ImGuiKey_8; 457 | case Qt::Key_9: 458 | return ImGuiKey_9; 459 | case Qt::Key_Semicolon: 460 | return ImGuiKey_Semicolon; 461 | case Qt::Key_Equal: 462 | return ImGuiKey_Equal; 463 | case Qt::Key_A: 464 | return ImGuiKey_A; 465 | case Qt::Key_B: 466 | return ImGuiKey_B; 467 | case Qt::Key_C: 468 | return ImGuiKey_C; 469 | case Qt::Key_D: 470 | return ImGuiKey_D; 471 | case Qt::Key_E: 472 | return ImGuiKey_E; 473 | case Qt::Key_F: 474 | return ImGuiKey_F; 475 | case Qt::Key_G: 476 | return ImGuiKey_G; 477 | case Qt::Key_H: 478 | return ImGuiKey_H; 479 | case Qt::Key_I: 480 | return ImGuiKey_I; 481 | case Qt::Key_J: 482 | return ImGuiKey_J; 483 | case Qt::Key_K: 484 | return ImGuiKey_K; 485 | case Qt::Key_L: 486 | return ImGuiKey_L; 487 | case Qt::Key_M: 488 | return ImGuiKey_M; 489 | case Qt::Key_N: 490 | return ImGuiKey_N; 491 | case Qt::Key_O: 492 | return ImGuiKey_O; 493 | case Qt::Key_P: 494 | return ImGuiKey_P; 495 | case Qt::Key_Q: 496 | return ImGuiKey_Q; 497 | case Qt::Key_R: 498 | return ImGuiKey_R; 499 | case Qt::Key_S: 500 | return ImGuiKey_S; 501 | case Qt::Key_T: 502 | return ImGuiKey_T; 503 | case Qt::Key_U: 504 | return ImGuiKey_U; 505 | case Qt::Key_V: 506 | return ImGuiKey_V; 507 | case Qt::Key_W: 508 | return ImGuiKey_W; 509 | case Qt::Key_X: 510 | return ImGuiKey_X; 511 | case Qt::Key_Y: 512 | return ImGuiKey_Y; 513 | case Qt::Key_Z: 514 | return ImGuiKey_Z; 515 | case Qt::Key_BracketLeft: 516 | return ImGuiKey_LeftBracket; 517 | case Qt::Key_Backslash: 518 | return ImGuiKey_Backslash; 519 | case Qt::Key_BracketRight: 520 | return ImGuiKey_RightBracket; 521 | case Qt::Key_QuoteLeft: 522 | return ImGuiKey_GraveAccent; 523 | case Qt::Key_Escape: 524 | return ImGuiKey_Escape; 525 | case Qt::Key_Tab: 526 | return ImGuiKey_Tab; 527 | case Qt::Key_Backspace: 528 | return ImGuiKey_Backspace; 529 | case Qt::Key_Return: 530 | case Qt::Key_Enter: 531 | return ImGuiKey_Enter; 532 | case Qt::Key_Insert: 533 | return ImGuiKey_Insert; 534 | case Qt::Key_Delete: 535 | return ImGuiKey_Delete; 536 | case Qt::Key_Pause: 537 | return ImGuiKey_Pause; 538 | case Qt::Key_Print: 539 | return ImGuiKey_PrintScreen; 540 | case Qt::Key_Home: 541 | return ImGuiKey_Home; 542 | case Qt::Key_End: 543 | return ImGuiKey_End; 544 | case Qt::Key_Left: 545 | return ImGuiKey_LeftArrow; 546 | case Qt::Key_Up: 547 | return ImGuiKey_UpArrow; 548 | case Qt::Key_Right: 549 | return ImGuiKey_RightArrow; 550 | case Qt::Key_Down: 551 | return ImGuiKey_DownArrow; 552 | case Qt::Key_PageUp: 553 | return ImGuiKey_PageUp; 554 | case Qt::Key_PageDown: 555 | return ImGuiKey_PageDown; 556 | case Qt::Key_Shift: 557 | return ImGuiKey_LeftShift; 558 | case Qt::Key_Control: 559 | return ImGuiKey_LeftCtrl; 560 | case Qt::Key_Meta: 561 | return ImGuiKey_LeftSuper; 562 | case Qt::Key_Alt: 563 | return ImGuiKey_LeftAlt; 564 | case Qt::Key_CapsLock: 565 | return ImGuiKey_CapsLock; 566 | case Qt::Key_NumLock: 567 | return ImGuiKey_NumLock; 568 | case Qt::Key_ScrollLock: 569 | return ImGuiKey_ScrollLock; 570 | case Qt::Key_F1: 571 | return ImGuiKey_F1; 572 | case Qt::Key_F2: 573 | return ImGuiKey_F2; 574 | case Qt::Key_F3: 575 | return ImGuiKey_F3; 576 | case Qt::Key_F4: 577 | return ImGuiKey_F4; 578 | case Qt::Key_F5: 579 | return ImGuiKey_F5; 580 | case Qt::Key_F6: 581 | return ImGuiKey_F6; 582 | case Qt::Key_F7: 583 | return ImGuiKey_F7; 584 | case Qt::Key_F8: 585 | return ImGuiKey_F8; 586 | case Qt::Key_F9: 587 | return ImGuiKey_F9; 588 | case Qt::Key_F10: 589 | return ImGuiKey_F10; 590 | case Qt::Key_F11: 591 | return ImGuiKey_F11; 592 | case Qt::Key_F12: 593 | return ImGuiKey_F12; 594 | default: 595 | break; 596 | } 597 | return ImGuiKey_None; 598 | } 599 | 600 | bool QRhiImgui::processEvent(QEvent *event) 601 | { 602 | ImGui::SetCurrentContext(static_cast(context)); 603 | ImGuiIO &io(ImGui::GetIO()); 604 | 605 | switch (event->type()) { 606 | case QEvent::MouseButtonPress: 607 | { 608 | QMouseEvent *me = static_cast(event); 609 | updateKeyboardModifiers(me->modifiers()); 610 | Qt::MouseButtons buttons = me->buttons(); 611 | if (buttons.testFlag(Qt::LeftButton) && !pressedMouseButtons.testFlag(Qt::LeftButton)) 612 | io.AddMouseButtonEvent(0, true); 613 | if (buttons.testFlag(Qt::RightButton) && !pressedMouseButtons.testFlag(Qt::RightButton)) 614 | io.AddMouseButtonEvent(1, true); 615 | if (buttons.testFlag(Qt::MiddleButton) && !pressedMouseButtons.testFlag(Qt::MiddleButton)) 616 | io.AddMouseButtonEvent(2, true); 617 | pressedMouseButtons = buttons; 618 | } 619 | return true; 620 | 621 | case QEvent::MouseButtonRelease: 622 | { 623 | QMouseEvent *me = static_cast(event); 624 | Qt::MouseButtons buttons = me->buttons(); 625 | if (!buttons.testFlag(Qt::LeftButton) && pressedMouseButtons.testFlag(Qt::LeftButton)) 626 | io.AddMouseButtonEvent(0, false); 627 | if (!buttons.testFlag(Qt::RightButton) && pressedMouseButtons.testFlag(Qt::RightButton)) 628 | io.AddMouseButtonEvent(1, false); 629 | if (!buttons.testFlag(Qt::MiddleButton) && pressedMouseButtons.testFlag(Qt::MiddleButton)) 630 | io.AddMouseButtonEvent(2, false); 631 | pressedMouseButtons = buttons; 632 | } 633 | return true; 634 | 635 | case QEvent::MouseMove: 636 | { 637 | QMouseEvent *me = static_cast(event); 638 | const QPointF pos = me->position(); 639 | io.AddMousePosEvent(pos.x(), pos.y()); 640 | } 641 | return true; 642 | 643 | case QEvent::Wheel: 644 | { 645 | QWheelEvent *we = static_cast(event); 646 | QPointF wheel(we->angleDelta().x() / 120.0f, we->angleDelta().y() / 120.0f); 647 | io.AddMouseWheelEvent(wheel.x(), wheel.y()); 648 | } 649 | return true; 650 | 651 | case QEvent::KeyPress: 652 | case QEvent::KeyRelease: 653 | { 654 | QKeyEvent *ke = static_cast(event); 655 | const bool down = event->type() == QEvent::KeyPress; 656 | updateKeyboardModifiers(ke->modifiers()); 657 | io.AddKeyEvent(mapKey(ke->key()), down); 658 | if (down && !ke->text().isEmpty()) { 659 | const QByteArray text = ke->text().toUtf8(); 660 | io.AddInputCharactersUTF8(text.constData()); 661 | } 662 | } 663 | return true; 664 | 665 | default: 666 | break; 667 | } 668 | 669 | return false; 670 | } 671 | 672 | QT_END_NAMESPACE 673 | -------------------------------------------------------------------------------- /imgui/qrhiimgui.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #ifndef QRHIIMGUI_H 5 | #define QRHIIMGUI_H 6 | 7 | #include 8 | 9 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 10 | #include 11 | #else 12 | #include 13 | #include 14 | #if QT_CONFIG(opengl) 15 | #include 16 | #endif 17 | #if QT_CONFIG(vulkan) 18 | #include 19 | #endif 20 | #ifdef Q_OS_WIN 21 | #include 22 | #endif 23 | #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) 24 | #include 25 | #endif 26 | #endif 27 | 28 | QT_BEGIN_NAMESPACE 29 | 30 | class QEvent; 31 | 32 | class QRhiImguiRenderer 33 | { 34 | public: 35 | ~QRhiImguiRenderer(); 36 | 37 | struct CmdListBuffer { 38 | quint32 offset; 39 | QByteArray data; 40 | }; 41 | 42 | struct DrawCmd { 43 | int cmdListBufferIdx; 44 | void *textureId; 45 | quint32 indexOffset; 46 | quint32 elemCount; 47 | QPointF itemPixelOffset; 48 | QVector4D clipRect; 49 | }; 50 | 51 | struct StaticRenderData { 52 | QImage fontTextureData; 53 | bool isValid() const { return !fontTextureData.isNull(); } 54 | void reset() { fontTextureData = QImage(); } 55 | }; 56 | 57 | struct FrameRenderData { 58 | quint32 totalVbufSize = 0; 59 | quint32 totalIbufSize = 0; 60 | QVarLengthArray vbuf; 61 | QVarLengthArray ibuf; 62 | QVarLengthArray draw; 63 | QSize outputPixelSize; 64 | }; 65 | 66 | StaticRenderData sf; 67 | FrameRenderData f; 68 | 69 | void prepare(QRhi *rhi, 70 | QRhiRenderTarget *rt, 71 | QRhiCommandBuffer *cb, 72 | const QMatrix4x4 &mvp, 73 | float opacity = 1.0f, 74 | float hdrWhiteLevelMultiplierOrZeroForSDRsRGB = 0.0f); 75 | void render(); 76 | void releaseResources(); 77 | 78 | enum CustomTextureOwnership { 79 | TakeCustomTextureOwnership, 80 | NoCustomTextureOwnership 81 | }; 82 | void registerCustomTexture(void *id, 83 | QRhiTexture *texture, 84 | QRhiSampler::Filter filter, 85 | CustomTextureOwnership ownership); 86 | 87 | private: 88 | QRhi *m_rhi = nullptr; 89 | QRhiRenderTarget *m_rt = nullptr; 90 | QRhiCommandBuffer *m_cb = nullptr; 91 | 92 | std::unique_ptr m_vbuf; 93 | std::unique_ptr m_ibuf; 94 | std::unique_ptr m_ubuf; 95 | std::unique_ptr m_ps; 96 | QVector m_renderPassFormat; 97 | std::unique_ptr m_linearSampler; 98 | std::unique_ptr m_nearestSampler; 99 | 100 | struct Texture { 101 | QImage image; 102 | QRhiTexture *tex = nullptr; 103 | QRhiShaderResourceBindings *srb = nullptr; 104 | QRhiSampler::Filter filter = QRhiSampler::Linear; 105 | bool ownTex = true; 106 | }; 107 | QHash m_textures; 108 | }; 109 | 110 | class QRhiImgui 111 | { 112 | public: 113 | QRhiImgui(); 114 | ~QRhiImgui(); 115 | 116 | using FrameFunc = std::function; 117 | void nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPointF &logicalOffset, FrameFunc frameFunc); 118 | void syncRenderer(QRhiImguiRenderer *renderer); 119 | bool processEvent(QEvent *e); 120 | 121 | void rebuildFontAtlas(); 122 | void rebuildFontAtlasWithFont(const QString &filename); 123 | 124 | private: 125 | void *context; 126 | QRhiImguiRenderer::StaticRenderData sf; 127 | QRhiImguiRenderer::FrameRenderData f; 128 | Qt::MouseButtons pressedMouseButtons; 129 | }; 130 | 131 | QT_END_NAMESPACE 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /imgui/qrhiimguiitem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include "qrhiimguiitem.h" 5 | #include "qrhiimgui.h" 6 | #include 7 | #include 8 | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | #include "imgui.h" 15 | 16 | QT_BEGIN_NAMESPACE 17 | 18 | // QSGRenderNode::projectionMatrix() is only in 6.5+, earlier versions only 19 | // have it in the RenderState and that's not available in prepare() 20 | #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) 21 | #define WELL_BEHAVING_DEPTH 1 22 | #endif 23 | 24 | struct QRhiImguiNode : public QSGRenderNode 25 | { 26 | QRhiImguiNode(QQuickWindow *window, QRhiImguiItem *item); 27 | ~QRhiImguiNode(); 28 | 29 | void prepare() override; 30 | void render(const RenderState *state) override; 31 | void releaseResources() override; 32 | StateFlags changedStates() const override; 33 | RenderingFlags flags() const override; 34 | 35 | QQuickWindow *window; 36 | QRhiImguiItem *item; 37 | QRhiImguiRenderer *renderer; 38 | QRhiImguiItemCustomRenderer *customRenderer = nullptr; 39 | }; 40 | 41 | QRhiImguiNode::QRhiImguiNode(QQuickWindow *window, QRhiImguiItem *item) 42 | : window(window), 43 | item(item), 44 | renderer(new QRhiImguiRenderer) 45 | { 46 | customRenderer = item->createCustomRenderer(); 47 | } 48 | 49 | QRhiImguiNode::~QRhiImguiNode() 50 | { 51 | delete customRenderer; 52 | delete renderer; 53 | } 54 | 55 | void QRhiImguiNode::releaseResources() 56 | { 57 | renderer->releaseResources(); 58 | } 59 | 60 | void QRhiImguiNode::prepare() 61 | { 62 | #if QT_VERSION_MAJOR > 6 || QT_VERSION_MINOR >= 6 63 | QRhi *rhi = window->rhi(); 64 | #else 65 | QSGRendererInterface *rif = window->rendererInterface(); 66 | QRhi *rhi = static_cast(rif->getResource(window, QSGRendererInterface::RhiResource)); 67 | #endif 68 | if (!rhi) { 69 | qWarning("QRhiImguiNode: No QRhi found for window %p", window); 70 | return; 71 | } 72 | 73 | if (customRenderer) 74 | customRenderer->render(); 75 | 76 | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) 77 | QRhiRenderTarget *rt = renderTarget(); 78 | QRhiCommandBuffer *cb = commandBuffer(); 79 | #else 80 | QSGRenderNodePrivate *d = QSGRenderNodePrivate::get(this); 81 | QRhiRenderTarget *rt = d->m_rt.rt; 82 | QRhiCommandBuffer *cb = d->m_rt.cb; 83 | #endif 84 | 85 | #if WELL_BEHAVING_DEPTH 86 | const QMatrix4x4 mvp = *projectionMatrix() * *matrix(); 87 | #else 88 | QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); 89 | const QSize outputSize = rt->pixelSize(); 90 | const float dpr = rt->devicePixelRatio(); 91 | mvp.ortho(0, outputSize.width() / dpr, outputSize.height() / dpr, 0, 1, -1); 92 | mvp *= *matrix(); 93 | #endif 94 | 95 | const float opacity = inheritedOpacity(); 96 | 97 | renderer->prepare(rhi, rt, cb, mvp, opacity); 98 | } 99 | 100 | void QRhiImguiNode::render(const RenderState *) 101 | { 102 | renderer->render(); 103 | } 104 | 105 | QSGRenderNode::StateFlags QRhiImguiNode::changedStates() const 106 | { 107 | return DepthState | ScissorState | ColorState | BlendState | CullState | ViewportState; 108 | } 109 | 110 | QSGRenderNode::RenderingFlags QRhiImguiNode::flags() const 111 | { 112 | // Don't want rhi->begin/endExternal() to be called by Quick since we work 113 | // with QRhi. 114 | QSGRenderNode::RenderingFlags result = NoExternalRendering; 115 | 116 | #if WELL_BEHAVING_DEPTH 117 | // If we take the projectionMatrix() adjustments into account then can 118 | // report DepthAwareRendering and so QQ will not disable the opaque pass. 119 | // (otherwise a visible QRhiImguiNode forces all batches to be part of the 120 | // back-to-front no-depth-write pass -> less optimal) 121 | result |= DepthAwareRendering; 122 | #endif 123 | 124 | return result; 125 | } 126 | 127 | struct QRhiImguiItemPrivate 128 | { 129 | QRhiImguiItem *q; 130 | QQuickWindow *window = nullptr; 131 | QMetaObject::Connection windowConn; 132 | QRhiImgui gui; 133 | bool showDemoWindow = true; 134 | 135 | QRhiImguiItemPrivate(QRhiImguiItem *item) : q(item) { } 136 | }; 137 | 138 | QRhiImguiItem::QRhiImguiItem(QQuickItem *parent) 139 | : QQuickItem(parent), 140 | d(new QRhiImguiItemPrivate(this)) 141 | { 142 | setFlag(ItemHasContents, true); 143 | setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); 144 | setAcceptHoverEvents(true); 145 | } 146 | 147 | QRhiImguiItem::~QRhiImguiItem() 148 | { 149 | delete d; 150 | } 151 | 152 | QSGNode *QRhiImguiItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) 153 | { 154 | // render thread, with main thread blocked 155 | 156 | if (size().isEmpty()) { 157 | delete node; 158 | return nullptr; 159 | } 160 | 161 | QRhiImguiNode *n = static_cast(node); 162 | if (!n) 163 | n = new QRhiImguiNode(d->window, this); 164 | 165 | d->gui.syncRenderer(n->renderer); 166 | 167 | if (n->customRenderer) 168 | n->customRenderer->sync(n->renderer); 169 | 170 | n->markDirty(QSGNode::DirtyMaterial); 171 | return n; 172 | } 173 | 174 | void QRhiImguiItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &changeData) 175 | { 176 | if (change == QQuickItem::ItemSceneChange) { 177 | if (d->window) { 178 | disconnect(d->windowConn); 179 | d->window = nullptr; 180 | } 181 | if (changeData.window) { 182 | d->window = window(); 183 | d->windowConn = connect(d->window, &QQuickWindow::afterAnimating, d->window, [this] { 184 | if (isVisible()) { 185 | d->gui.nextFrame(size(), 186 | d->window->effectiveDevicePixelRatio(), 187 | mapToScene(QPointF(0, 0)), 188 | [this] { frame(); }); 189 | update(); 190 | if (!d->window->isSceneGraphInitialized()) 191 | d->window->update(); 192 | } 193 | }); 194 | } 195 | } 196 | } 197 | 198 | void QRhiImguiItem::keyPressEvent(QKeyEvent *event) 199 | { 200 | d->gui.processEvent(event); 201 | } 202 | 203 | void QRhiImguiItem::keyReleaseEvent(QKeyEvent *event) 204 | { 205 | d->gui.processEvent(event); 206 | } 207 | 208 | void QRhiImguiItem::mousePressEvent(QMouseEvent *event) 209 | { 210 | forceActiveFocus(Qt::MouseFocusReason); 211 | d->gui.processEvent(event); 212 | } 213 | 214 | void QRhiImguiItem::mouseMoveEvent(QMouseEvent *event) 215 | { 216 | d->gui.processEvent(event); 217 | } 218 | 219 | void QRhiImguiItem::mouseReleaseEvent(QMouseEvent *event) 220 | { 221 | d->gui.processEvent(event); 222 | } 223 | 224 | void QRhiImguiItem::mouseDoubleClickEvent(QMouseEvent *event) 225 | { 226 | d->gui.processEvent(event); 227 | } 228 | 229 | void QRhiImguiItem::wheelEvent(QWheelEvent *event) 230 | { 231 | d->gui.processEvent(event); 232 | } 233 | 234 | void QRhiImguiItem::hoverMoveEvent(QHoverEvent *event) 235 | { 236 | // Simulate the QWindow behavior, which means sending moves even when no 237 | // button is down. 238 | 239 | if (QGuiApplication::mouseButtons() != Qt::NoButton) 240 | return; 241 | 242 | const QPointF sceneOffset = mapToScene(event->position()); 243 | const QPointF globalOffset = mapToGlobal(event->position()); 244 | QMouseEvent e(QEvent::MouseMove, event->position(), event->position() + sceneOffset, event->position() + globalOffset, 245 | Qt::NoButton, Qt::NoButton, QGuiApplication::keyboardModifiers()); 246 | d->gui.processEvent(&e); 247 | } 248 | 249 | void QRhiImguiItem::touchEvent(QTouchEvent *event) 250 | { 251 | d->gui.processEvent(event); 252 | } 253 | 254 | QRhiImgui *QRhiImguiItem::imgui() 255 | { 256 | return &d->gui; 257 | } 258 | 259 | void QRhiImguiItem::frame() 260 | { 261 | ImGui::ShowDemoWindow(&d->showDemoWindow); 262 | } 263 | 264 | QRhiImguiItemCustomRenderer *QRhiImguiItem::createCustomRenderer() 265 | { 266 | // Called on the render thread (if there is one) with the main thread blocked. 267 | 268 | return nullptr; 269 | } 270 | 271 | QRhiImguiItemCustomRenderer::~QRhiImguiItemCustomRenderer() 272 | { 273 | // Called on the render thread (if there is one) when the QRhiImguiRenderer (and the scenegraph node) is going away. 274 | // This is convenient to safely release QRhi* objects created in sync() and customRender(). 275 | // The QRhiImguiItem (living on the main thread) may or may not anymore exist at this point. 276 | } 277 | 278 | void QRhiImguiItemCustomRenderer::sync(QRhiImguiRenderer *) 279 | { 280 | // Called on the render thread (if there is one) with the main thread blocked. 281 | } 282 | 283 | void QRhiImguiItemCustomRenderer::render() 284 | { 285 | // Called on the render thread (if there is one) whenever the ImGui renderer is starting a new frame. 286 | // Called with a frame being recorded, but without an active render pass. 287 | } 288 | 289 | QT_END_NAMESPACE 290 | -------------------------------------------------------------------------------- /imgui/qrhiimguiitem.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #ifndef QRHIIMGUIITEM_H 5 | #define QRHIIMGUIITEM_H 6 | 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | 11 | struct QRhiImguiItemPrivate; 12 | class QRhiImgui; 13 | class QRhiImguiRenderer; 14 | 15 | class QRhiImguiItemCustomRenderer 16 | { 17 | public: 18 | virtual ~QRhiImguiItemCustomRenderer(); 19 | virtual void sync(QRhiImguiRenderer *renderer); 20 | virtual void render(); 21 | }; 22 | 23 | class QRhiImguiItem : public QQuickItem 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | QRhiImguiItem(QQuickItem *parent = nullptr); 29 | ~QRhiImguiItem(); 30 | 31 | virtual void frame(); 32 | virtual QRhiImguiItemCustomRenderer *createCustomRenderer(); 33 | 34 | QRhiImgui *imgui(); 35 | 36 | private: 37 | QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override; 38 | void itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData &) override; 39 | 40 | void keyPressEvent(QKeyEvent *event) override; 41 | void keyReleaseEvent(QKeyEvent *event) override; 42 | void mousePressEvent(QMouseEvent *event) override; 43 | void mouseMoveEvent(QMouseEvent *event) override; 44 | void mouseReleaseEvent(QMouseEvent *event) override; 45 | void mouseDoubleClickEvent(QMouseEvent *event) override; 46 | void wheelEvent(QWheelEvent *event) override; 47 | void hoverMoveEvent(QHoverEvent *event) override; 48 | void touchEvent(QTouchEvent *event) override; 49 | 50 | QRhiImguiItemPrivate *d; 51 | }; 52 | 53 | QT_END_NAMESPACE 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui2/a2326ae432d6d44ce8fc418e45b40cfb0f69f08e/screenshot.png --------------------------------------------------------------------------------