├── ImGuiColorTextEdit ├── LICENSE ├── TextEditor.cpp └── TextEditor.h ├── README.md ├── buildshaders.bat ├── color.frag ├── color.frag.qsb ├── color.vert ├── color.vert.qsb ├── imgui.frag ├── imgui.frag.qsb ├── imgui.vert ├── imgui.vert.qsb ├── imgui ├── LICENSE.txt ├── imconfig.h ├── imgui.cpp ├── imgui.h ├── imgui_demo.cpp ├── imgui_draw.cpp ├── imgui_internal.h ├── imgui_widgets.cpp ├── imstb_rectpack.h ├── imstb_textedit.h └── imstb_truetype.h ├── imnodes ├── LICENSE.md ├── imnodes.cpp └── imnodes.h ├── qrhiimgui.cpp ├── qrhiimgui.h ├── qrhiimgui.pro ├── qrhiimgui_p.h ├── quick ├── globalstate.h ├── gui.cpp ├── gui.h ├── imguidemo.qrc ├── main.cpp ├── main.qml └── quick.pro ├── qwindow ├── examplefw.h ├── imguidemo.cpp ├── imguidemo.qrc └── qwindow.pro └── screenshot.png /ImGuiColorTextEdit/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BalazsJako 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 | -------------------------------------------------------------------------------- /ImGuiColorTextEdit/TextEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "imgui.h" 12 | 13 | class TextEditor 14 | { 15 | public: 16 | enum class PaletteIndex 17 | { 18 | Default, 19 | Keyword, 20 | Number, 21 | String, 22 | CharLiteral, 23 | Punctuation, 24 | Preprocessor, 25 | Identifier, 26 | KnownIdentifier, 27 | PreprocIdentifier, 28 | Comment, 29 | MultiLineComment, 30 | Background, 31 | Cursor, 32 | Selection, 33 | ErrorMarker, 34 | Breakpoint, 35 | LineNumber, 36 | CurrentLineFill, 37 | CurrentLineFillInactive, 38 | CurrentLineEdge, 39 | Max 40 | }; 41 | 42 | enum class SelectionMode 43 | { 44 | Normal, 45 | Word, 46 | Line 47 | }; 48 | 49 | struct Breakpoint 50 | { 51 | int mLine; 52 | bool mEnabled; 53 | std::string mCondition; 54 | 55 | Breakpoint() 56 | : mLine(-1) 57 | , mEnabled(false) 58 | {} 59 | }; 60 | 61 | // Represents a character coordinate from the user's point of view, 62 | // i. e. consider an uniform grid (assuming fixed-width font) on the 63 | // screen as it is rendered, and each cell has its own coordinate, starting from 0. 64 | // Tabs are counted as [1..mTabSize] count empty spaces, depending on 65 | // how many space is necessary to reach the next tab stop. 66 | // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4, 67 | // because it is rendered as " ABC" on the screen. 68 | struct Coordinates 69 | { 70 | int mLine, mColumn; 71 | Coordinates() : mLine(0), mColumn(0) {} 72 | Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) 73 | { 74 | assert(aLine >= 0); 75 | assert(aColumn >= 0); 76 | } 77 | static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; } 78 | 79 | bool operator ==(const Coordinates& o) const 80 | { 81 | return 82 | mLine == o.mLine && 83 | mColumn == o.mColumn; 84 | } 85 | 86 | bool operator !=(const Coordinates& o) const 87 | { 88 | return 89 | mLine != o.mLine || 90 | mColumn != o.mColumn; 91 | } 92 | 93 | bool operator <(const Coordinates& o) const 94 | { 95 | if (mLine != o.mLine) 96 | return mLine < o.mLine; 97 | return mColumn < o.mColumn; 98 | } 99 | 100 | bool operator >(const Coordinates& o) const 101 | { 102 | if (mLine != o.mLine) 103 | return mLine > o.mLine; 104 | return mColumn > o.mColumn; 105 | } 106 | 107 | bool operator <=(const Coordinates& o) const 108 | { 109 | if (mLine != o.mLine) 110 | return mLine < o.mLine; 111 | return mColumn <= o.mColumn; 112 | } 113 | 114 | bool operator >=(const Coordinates& o) const 115 | { 116 | if (mLine != o.mLine) 117 | return mLine > o.mLine; 118 | return mColumn >= o.mColumn; 119 | } 120 | }; 121 | 122 | struct Identifier 123 | { 124 | Coordinates mLocation; 125 | std::string mDeclaration; 126 | }; 127 | 128 | typedef std::string String; 129 | typedef std::unordered_map Identifiers; 130 | typedef std::unordered_set Keywords; 131 | typedef std::map ErrorMarkers; 132 | typedef std::unordered_set Breakpoints; 133 | typedef std::array Palette; 134 | typedef uint8_t Char; 135 | 136 | struct Glyph 137 | { 138 | Char mChar; 139 | PaletteIndex mColorIndex = PaletteIndex::Default; 140 | bool mComment : 1; 141 | bool mMultiLineComment : 1; 142 | bool mPreprocessor : 1; 143 | 144 | Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), 145 | mComment(false), mMultiLineComment(false), mPreprocessor(false) {} 146 | }; 147 | 148 | typedef std::vector Line; 149 | typedef std::vector Lines; 150 | 151 | struct LanguageDefinition 152 | { 153 | typedef std::pair TokenRegexString; 154 | typedef std::vector TokenRegexStrings; 155 | typedef bool(*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex); 156 | 157 | std::string mName; 158 | Keywords mKeywords; 159 | Identifiers mIdentifiers; 160 | Identifiers mPreprocIdentifiers; 161 | std::string mCommentStart, mCommentEnd, mSingleLineComment; 162 | char mPreprocChar; 163 | bool mAutoIndentation; 164 | 165 | TokenizeCallback mTokenize; 166 | 167 | TokenRegexStrings mTokenRegexStrings; 168 | 169 | bool mCaseSensitive; 170 | 171 | LanguageDefinition() 172 | : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) 173 | { 174 | } 175 | 176 | static const LanguageDefinition& CPlusPlus(); 177 | static const LanguageDefinition& HLSL(); 178 | static const LanguageDefinition& GLSL(); 179 | static const LanguageDefinition& C(); 180 | static const LanguageDefinition& SQL(); 181 | static const LanguageDefinition& AngelScript(); 182 | static const LanguageDefinition& Lua(); 183 | }; 184 | 185 | TextEditor(); 186 | ~TextEditor(); 187 | 188 | void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); 189 | const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; } 190 | 191 | const Palette& GetPalette() const { return mPaletteBase; } 192 | void SetPalette(const Palette& aValue); 193 | 194 | void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } 195 | void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } 196 | 197 | void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); 198 | void SetText(const std::string& aText); 199 | std::string GetText() const; 200 | 201 | void SetTextLines(const std::vector& aLines); 202 | std::vector GetTextLines() const; 203 | 204 | std::string GetSelectedText() const; 205 | std::string GetCurrentLineText()const; 206 | 207 | int GetTotalLines() const { return (int)mLines.size(); } 208 | bool IsOverwrite() const { return mOverwrite; } 209 | 210 | void SetReadOnly(bool aValue); 211 | bool IsReadOnly() const { return mReadOnly; } 212 | bool IsTextChanged() const { return mTextChanged; } 213 | bool IsCursorPositionChanged() const { return mCursorPositionChanged; } 214 | 215 | bool IsColorizerEnabled() const { return mColorizerEnabled; } 216 | void SetColorizerEnable(bool aValue); 217 | 218 | Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } 219 | void SetCursorPosition(const Coordinates& aPosition); 220 | 221 | inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;} 222 | inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; } 223 | 224 | inline void SetHandleKeyboardInputs (bool aValue){ mHandleKeyboardInputs = aValue;} 225 | inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; } 226 | 227 | inline void SetImGuiChildIgnored (bool aValue){ mIgnoreImGuiChild = aValue;} 228 | inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; } 229 | 230 | inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; } 231 | inline bool IsShowingWhitespaces() const { return mShowWhitespaces; } 232 | 233 | void SetTabSize(int aValue); 234 | inline int GetTabSize() const { return mTabSize; } 235 | 236 | void InsertText(const std::string& aValue); 237 | void InsertText(const char* aValue); 238 | 239 | void MoveUp(int aAmount = 1, bool aSelect = false); 240 | void MoveDown(int aAmount = 1, bool aSelect = false); 241 | void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); 242 | void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); 243 | void MoveTop(bool aSelect = false); 244 | void MoveBottom(bool aSelect = false); 245 | void MoveHome(bool aSelect = false); 246 | void MoveEnd(bool aSelect = false); 247 | 248 | void SetSelectionStart(const Coordinates& aPosition); 249 | void SetSelectionEnd(const Coordinates& aPosition); 250 | void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal); 251 | void SelectWordUnderCursor(); 252 | void SelectAll(); 253 | bool HasSelection() const; 254 | 255 | void Copy(); 256 | void Cut(); 257 | void Paste(); 258 | void Delete(); 259 | 260 | bool CanUndo() const; 261 | bool CanRedo() const; 262 | void Undo(int aSteps = 1); 263 | void Redo(int aSteps = 1); 264 | 265 | static const Palette& GetDarkPalette(); 266 | static const Palette& GetLightPalette(); 267 | static const Palette& GetRetroBluePalette(); 268 | 269 | private: 270 | typedef std::vector> RegexList; 271 | 272 | struct EditorState 273 | { 274 | Coordinates mSelectionStart; 275 | Coordinates mSelectionEnd; 276 | Coordinates mCursorPosition; 277 | }; 278 | 279 | class UndoRecord 280 | { 281 | public: 282 | UndoRecord() {} 283 | ~UndoRecord() {} 284 | 285 | UndoRecord( 286 | const std::string& aAdded, 287 | const TextEditor::Coordinates aAddedStart, 288 | const TextEditor::Coordinates aAddedEnd, 289 | 290 | const std::string& aRemoved, 291 | const TextEditor::Coordinates aRemovedStart, 292 | const TextEditor::Coordinates aRemovedEnd, 293 | 294 | TextEditor::EditorState& aBefore, 295 | TextEditor::EditorState& aAfter); 296 | 297 | void Undo(TextEditor* aEditor); 298 | void Redo(TextEditor* aEditor); 299 | 300 | std::string mAdded; 301 | Coordinates mAddedStart; 302 | Coordinates mAddedEnd; 303 | 304 | std::string mRemoved; 305 | Coordinates mRemovedStart; 306 | Coordinates mRemovedEnd; 307 | 308 | EditorState mBefore; 309 | EditorState mAfter; 310 | }; 311 | 312 | typedef std::vector UndoBuffer; 313 | 314 | void ProcessInputs(); 315 | void Colorize(int aFromLine = 0, int aCount = -1); 316 | void ColorizeRange(int aFromLine = 0, int aToLine = 0); 317 | void ColorizeInternal(); 318 | float TextDistanceToLineStart(const Coordinates& aFrom) const; 319 | void EnsureCursorVisible(); 320 | int GetPageSize() const; 321 | std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; 322 | Coordinates GetActualCursorCoordinates() const; 323 | Coordinates SanitizeCoordinates(const Coordinates& aValue) const; 324 | void Advance(Coordinates& aCoordinates) const; 325 | void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); 326 | int InsertTextAt(Coordinates& aWhere, const char* aValue); 327 | void AddUndo(UndoRecord& aValue); 328 | Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; 329 | Coordinates FindWordStart(const Coordinates& aFrom) const; 330 | Coordinates FindWordEnd(const Coordinates& aFrom) const; 331 | Coordinates FindNextWord(const Coordinates& aFrom) const; 332 | int GetCharacterIndex(const Coordinates& aCoordinates) const; 333 | int GetCharacterColumn(int aLine, int aIndex) const; 334 | int GetLineCharacterCount(int aLine) const; 335 | int GetLineMaxColumn(int aLine) const; 336 | bool IsOnWordBoundary(const Coordinates& aAt) const; 337 | void RemoveLine(int aStart, int aEnd); 338 | void RemoveLine(int aIndex); 339 | Line& InsertLine(int aIndex); 340 | void EnterCharacter(ImWchar aChar, bool aShift); 341 | void Backspace(); 342 | void DeleteSelection(); 343 | std::string GetWordUnderCursor() const; 344 | std::string GetWordAt(const Coordinates& aCoords) const; 345 | ImU32 GetGlyphColor(const Glyph& aGlyph) const; 346 | 347 | void HandleKeyboardInputs(); 348 | void HandleMouseInputs(); 349 | void Render(); 350 | 351 | float mLineSpacing; 352 | Lines mLines; 353 | EditorState mState; 354 | UndoBuffer mUndoBuffer; 355 | int mUndoIndex; 356 | 357 | int mTabSize; 358 | bool mOverwrite; 359 | bool mReadOnly; 360 | bool mWithinRender; 361 | bool mScrollToCursor; 362 | bool mScrollToTop; 363 | bool mTextChanged; 364 | bool mColorizerEnabled; 365 | float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. 366 | int mLeftMargin; 367 | bool mCursorPositionChanged; 368 | int mColorRangeMin, mColorRangeMax; 369 | SelectionMode mSelectionMode; 370 | bool mHandleKeyboardInputs; 371 | bool mHandleMouseInputs; 372 | bool mIgnoreImGuiChild; 373 | bool mShowWhitespaces; 374 | 375 | Palette mPaletteBase; 376 | Palette mPalette; 377 | LanguageDefinition mLanguageDefinition; 378 | RegexList mRegexList; 379 | 380 | bool mCheckComments; 381 | Breakpoints mBreakpoints; 382 | ErrorMarkers mErrorMarkers; 383 | ImVec2 mCharAdvance; 384 | Coordinates mInteractiveStart, mInteractiveEnd; 385 | std::string mLineBuffer; 386 | uint64_t mStartTime; 387 | 388 | float mLastClick; 389 | }; 390 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Modern examples of integrating [Dear ImGui](https://github.com/ocornut/imgui) 2 | with [Qt Quick](http://doc.qt.io/qt-5/qtquick-index.html). 3 | 4 | These serve both as 5 | * a PoC for integrating external engines (that do not render on its own but provide vertex and image data) in Qt 6, 6 | * and as a test bed to make experimenting with ImGui and related 3rd party widgets simple and easy. 7 | 8 | Here we use QRhi, the Qt Rendering Hardware Interface, to render. No direct 9 | OpenGL usage anymore. QRhi is a private (but exported) API in the foreseeable 10 | future, so the examples all pull in gui-private to get access to qrhi_p.h. They 11 | are expected to work identically with Vulkan, Metal, D3D11, and OpenGL, on all platforms supported by Qt 12 | 13 | There are two demos at the moment, one targeting a QWindow (so no widgets, no 14 | QML), and one as an overlay in a Qt Quick scene. The latter saves ~600 lines of 15 | setup code because QQuickWindow takes care of all that. 16 | 17 | When it comes to Qt Quick, this approach is different than what was taken in 18 | [imgui-qtquick, an older OpenGL and Qt 5 based 19 | implementation](https://github.com/alpqr/imgui-qtquick). That one used 20 | QSGRenderNode to get a true QQuickItem, while this one is an overlay that always 21 | renders after Quick's done with recording the draw calls for the scene. 22 | 23 | The Quick-based example needs the 'dev' branch (so Qt 6.0) of qtbase and 24 | qtdeclarative, and, at the time of writing, an additional [patch on 25 | top](https://codereview.qt-project.org/c/qt/qtdeclarative/+/302460) 26 | 27 | It also uses [imnodes](https://github.com/Nelarius/imnodes) and 28 | [ImGuiColorTextEdit](https://github.com/BalazsJako/ImGuiColorTextEdit) with some 29 | dummy content, just for fun. 30 | 31 | ![Screenshot](screenshot.png) 32 | 33 | The obligatory rotating red rectangle and some text in QML, with the ImGui overlay opened on top. 34 | -------------------------------------------------------------------------------- /buildshaders.bat: -------------------------------------------------------------------------------- 1 | qsb --glsl "100 es,120,150" --hlsl 50 -c --msl 12 imgui.vert -o imgui.vert.qsb 2 | qsb --glsl "100 es,120,150" --hlsl 50 -c --msl 12 imgui.frag -o imgui.frag.qsb 3 | 4 | qsb --glsl "100 es,120,150" --hlsl 50 -c --msl 12 color.vert -o color.vert.qsb 5 | qsb --glsl "100 es,120,150" --hlsl 50 -c --msl 12 color.frag -o color.frag.qsb 6 | -------------------------------------------------------------------------------- /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 | } ubuf; 11 | 12 | void main() 13 | { 14 | fragColor = vec4(v_color * ubuf.opacity, ubuf.opacity); 15 | } 16 | -------------------------------------------------------------------------------- /color.frag.qsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui/b737d519ca2702c6ab9fe1b431048c3e53774a3a/color.frag.qsb -------------------------------------------------------------------------------- /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 | } ubuf; 12 | 13 | out gl_PerVertex { vec4 gl_Position; }; 14 | 15 | void main() 16 | { 17 | v_color = color; 18 | gl_Position = ubuf.mvp * position; 19 | } 20 | -------------------------------------------------------------------------------- /color.vert.qsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui/b737d519ca2702c6ab9fe1b431048c3e53774a3a/color.vert.qsb -------------------------------------------------------------------------------- /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 | } ubuf; 12 | 13 | layout(binding = 1) uniform sampler2D tex; 14 | 15 | void main() 16 | { 17 | vec4 c = v_color * texture(tex, v_texcoord); 18 | fragColor = vec4(c.rgb, c.a * ubuf.opacity); 19 | } 20 | -------------------------------------------------------------------------------- /imgui.frag.qsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui/b737d519ca2702c6ab9fe1b431048c3e53774a3a/imgui.frag.qsb -------------------------------------------------------------------------------- /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 | } ubuf; 14 | 15 | out gl_PerVertex { vec4 gl_Position; }; 16 | 17 | void main() 18 | { 19 | v_texcoord = texcoord; 20 | v_color = color; 21 | gl_Position = ubuf.mvp * vec4(position.xy, 0.0, 1.0); 22 | } 23 | -------------------------------------------------------------------------------- /imgui.vert.qsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui/b737d519ca2702c6ab9fe1b431048c3e53774a3a/imgui.vert.qsb -------------------------------------------------------------------------------- /imgui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2020 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/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/branch with your modifications to imconfig.h) 7 | // B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" 8 | // If you do so you need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include 9 | // the imgui*.cpp files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 10 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 11 | // 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. 12 | //----------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | //---- Define assertion handler. Defaults to calling assert(). 17 | // 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. 18 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 19 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 20 | 21 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 22 | // 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. 23 | //#define IMGUI_API __declspec( dllexport ) 24 | //#define IMGUI_API __declspec( dllimport ) 25 | 26 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 27 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 28 | 29 | //---- Disable all of Dear ImGui or don't implement standard windows. 30 | // It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. 31 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 32 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. 33 | //#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty. 34 | 35 | //---- Don't implement some functions to reduce linkage requirements. 36 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 37 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 38 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 39 | //#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). 40 | //#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) 41 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 42 | //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. 43 | //#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(). 44 | 45 | //---- Include imgui_user.h at the end of imgui.h as a convenience 46 | //#define IMGUI_INCLUDE_IMGUI_USER_H 47 | 48 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 49 | //#define IMGUI_USE_BGRA_PACKED_COLOR 50 | 51 | //---- Use 32-bit for ImWchar (default is 16-bit) to support full unicode code points. 52 | //#define IMGUI_USE_WCHAR32 53 | 54 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 55 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 56 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 57 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 58 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 59 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 60 | 61 | //---- Unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined, use the much faster STB sprintf library implementation of vsnprintf instead of the one from the default C library. 62 | // Note that stb_sprintf.h is meant to be provided by the user and available in the include path at compile time. Also, the compatibility checks of the arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf. 63 | // #define IMGUI_USE_STB_SPRINTF 64 | 65 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 66 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 67 | /* 68 | #define IM_VEC2_CLASS_EXTRA \ 69 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 70 | operator MyVec2() const { return MyVec2(x,y); } 71 | 72 | #define IM_VEC4_CLASS_EXTRA \ 73 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 74 | operator MyVec4() const { return MyVec4(x,y,z,w); } 75 | */ 76 | 77 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 78 | // Your renderer back-end will need to support it (most example renderer back-ends support both 16/32-bit indices). 79 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 80 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 81 | #define ImDrawIdx unsigned int 82 | 83 | //---- Override ImDrawCallback signature (will need to modify renderer back-ends accordingly) 84 | //struct ImDrawList; 85 | //struct ImDrawCmd; 86 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 87 | //#define ImDrawCallback MyImDrawCallback 88 | 89 | //---- Debug Tools: Macro to break in Debugger 90 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 91 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 92 | //#define IM_DEBUG_BREAK __debugbreak() 93 | 94 | //---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), 95 | // (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) 96 | // This adds a small runtime cost which is why it is not enabled by default. 97 | //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX 98 | 99 | //---- Debug Tools: Enable slower asserts 100 | //#define IMGUI_DEBUG_PARANOID 101 | 102 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 103 | /* 104 | namespace ImGui 105 | { 106 | void MyFunction(const char* name, const MyMatrix44& v); 107 | } 108 | */ 109 | -------------------------------------------------------------------------------- /imgui/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.00. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Added STBRP__CDECL 5 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_rect_pack.h - v1.00 - public domain - rectangle packing 8 | // Sean Barrett 2014 9 | // 10 | // Useful for e.g. packing rectangular textures into an atlas. 11 | // Does not do rotation. 12 | // 13 | // Not necessarily the awesomest packing method, but better than 14 | // the totally naive one in stb_truetype (which is primarily what 15 | // this is meant to replace). 16 | // 17 | // Has only had a few tests run, may have issues. 18 | // 19 | // More docs to come. 20 | // 21 | // No memory allocations; uses qsort() and assert() from stdlib. 22 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 23 | // 24 | // This library currently uses the Skyline Bottom-Left algorithm. 25 | // 26 | // Please note: better rectangle packers are welcome! Please 27 | // implement them to the same API, but with a different init 28 | // function. 29 | // 30 | // Credits 31 | // 32 | // Library 33 | // Sean Barrett 34 | // Minor features 35 | // Martins Mozeiko 36 | // github:IntellectualKitty 37 | // 38 | // Bugfixes / warning fixes 39 | // Jeremy Jaussaud 40 | // Fabian Giesen 41 | // 42 | // Version history: 43 | // 44 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 45 | // 0.99 (2019-02-07) warning fixes 46 | // 0.11 (2017-03-03) return packing success/fail result 47 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 48 | // 0.09 (2016-08-27) fix compiler warnings 49 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 50 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 51 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 52 | // 0.05: added STBRP_ASSERT to allow replacing assert 53 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 54 | // 0.01: initial release 55 | // 56 | // LICENSE 57 | // 58 | // See end of file for license information. 59 | 60 | ////////////////////////////////////////////////////////////////////////////// 61 | // 62 | // INCLUDE SECTION 63 | // 64 | 65 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 66 | #define STB_INCLUDE_STB_RECT_PACK_H 67 | 68 | #define STB_RECT_PACK_VERSION 1 69 | 70 | #ifdef STBRP_STATIC 71 | #define STBRP_DEF static 72 | #else 73 | #define STBRP_DEF extern 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | extern "C" { 78 | #endif 79 | 80 | typedef struct stbrp_context stbrp_context; 81 | typedef struct stbrp_node stbrp_node; 82 | typedef struct stbrp_rect stbrp_rect; 83 | 84 | #ifdef STBRP_LARGE_RECTS 85 | typedef int stbrp_coord; 86 | #else 87 | typedef unsigned short stbrp_coord; 88 | #endif 89 | 90 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 91 | // Assign packed locations to rectangles. The rectangles are of type 92 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 93 | // are 'num_rects' many of them. 94 | // 95 | // Rectangles which are successfully packed have the 'was_packed' flag 96 | // set to a non-zero value and 'x' and 'y' store the minimum location 97 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 98 | // if you imagine y increasing downwards). Rectangles which do not fit 99 | // have the 'was_packed' flag set to 0. 100 | // 101 | // You should not try to access the 'rects' array from another thread 102 | // while this function is running, as the function temporarily reorders 103 | // the array while it executes. 104 | // 105 | // To pack into another rectangle, you need to call stbrp_init_target 106 | // again. To continue packing into the same rectangle, you can call 107 | // this function again. Calling this multiple times with multiple rect 108 | // arrays will probably produce worse packing results than calling it 109 | // a single time with the full rectangle array, but the option is 110 | // available. 111 | // 112 | // The function returns 1 if all of the rectangles were successfully 113 | // packed and 0 otherwise. 114 | 115 | struct stbrp_rect 116 | { 117 | // reserved for your use: 118 | int id; 119 | 120 | // input: 121 | stbrp_coord w, h; 122 | 123 | // output: 124 | stbrp_coord x, y; 125 | int was_packed; // non-zero if valid packing 126 | 127 | }; // 16 bytes, nominally 128 | 129 | 130 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 131 | // Initialize a rectangle packer to: 132 | // pack a rectangle that is 'width' by 'height' in dimensions 133 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 134 | // 135 | // You must call this function every time you start packing into a new target. 136 | // 137 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 138 | // the following stbrp_pack_rects() call (or calls), but can be freed after 139 | // the call (or calls) finish. 140 | // 141 | // Note: to guarantee best results, either: 142 | // 1. make sure 'num_nodes' >= 'width' 143 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 144 | // 145 | // If you don't do either of the above things, widths will be quantized to multiples 146 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 147 | // 148 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 149 | // may run out of temporary storage and be unable to pack some rectangles. 150 | 151 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 152 | // Optionally call this function after init but before doing any packing to 153 | // change the handling of the out-of-temp-memory scenario, described above. 154 | // If you call init again, this will be reset to the default (false). 155 | 156 | 157 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 158 | // Optionally select which packing heuristic the library should use. Different 159 | // heuristics will produce better/worse results for different data sets. 160 | // If you call init again, this will be reset to the default. 161 | 162 | enum 163 | { 164 | STBRP_HEURISTIC_Skyline_default=0, 165 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 166 | STBRP_HEURISTIC_Skyline_BF_sortHeight 167 | }; 168 | 169 | 170 | ////////////////////////////////////////////////////////////////////////////// 171 | // 172 | // the details of the following structures don't matter to you, but they must 173 | // be visible so you can handle the memory allocations for them 174 | 175 | struct stbrp_node 176 | { 177 | stbrp_coord x,y; 178 | stbrp_node *next; 179 | }; 180 | 181 | struct stbrp_context 182 | { 183 | int width; 184 | int height; 185 | int align; 186 | int init_mode; 187 | int heuristic; 188 | int num_nodes; 189 | stbrp_node *active_head; 190 | stbrp_node *free_head; 191 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 192 | }; 193 | 194 | #ifdef __cplusplus 195 | } 196 | #endif 197 | 198 | #endif 199 | 200 | ////////////////////////////////////////////////////////////////////////////// 201 | // 202 | // IMPLEMENTATION SECTION 203 | // 204 | 205 | #ifdef STB_RECT_PACK_IMPLEMENTATION 206 | #ifndef STBRP_SORT 207 | #include 208 | #define STBRP_SORT qsort 209 | #endif 210 | 211 | #ifndef STBRP_ASSERT 212 | #include 213 | #define STBRP_ASSERT assert 214 | #endif 215 | 216 | // [DEAR IMGUI] Added STBRP__CDECL 217 | #ifdef _MSC_VER 218 | #define STBRP__NOTUSED(v) (void)(v) 219 | #define STBRP__CDECL __cdecl 220 | #else 221 | #define STBRP__NOTUSED(v) (void)sizeof(v) 222 | #define STBRP__CDECL 223 | #endif 224 | 225 | enum 226 | { 227 | STBRP__INIT_skyline = 1 228 | }; 229 | 230 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 231 | { 232 | switch (context->init_mode) { 233 | case STBRP__INIT_skyline: 234 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 235 | context->heuristic = heuristic; 236 | break; 237 | default: 238 | STBRP_ASSERT(0); 239 | } 240 | } 241 | 242 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 243 | { 244 | if (allow_out_of_mem) 245 | // if it's ok to run out of memory, then don't bother aligning them; 246 | // this gives better packing, but may fail due to OOM (even though 247 | // the rectangles easily fit). @TODO a smarter approach would be to only 248 | // quantize once we've hit OOM, then we could get rid of this parameter. 249 | context->align = 1; 250 | else { 251 | // if it's not ok to run out of memory, then quantize the widths 252 | // so that num_nodes is always enough nodes. 253 | // 254 | // I.e. num_nodes * align >= width 255 | // align >= width / num_nodes 256 | // align = ceil(width/num_nodes) 257 | 258 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 259 | } 260 | } 261 | 262 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 263 | { 264 | int i; 265 | #ifndef STBRP_LARGE_RECTS 266 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 267 | #endif 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 | #ifdef STBRP_LARGE_RECTS 287 | context->extra[1].y = (1<<30); 288 | #else 289 | context->extra[1].y = 65535; 290 | #endif 291 | context->extra[1].next = NULL; 292 | } 293 | 294 | // find minimum y position if it starts at x1 295 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 296 | { 297 | stbrp_node *node = first; 298 | int x1 = x0 + width; 299 | int min_y, visited_width, waste_area; 300 | 301 | STBRP__NOTUSED(c); 302 | 303 | STBRP_ASSERT(first->x <= x0); 304 | 305 | #if 0 306 | // skip in case we're past the node 307 | while (node->next->x <= x0) 308 | ++node; 309 | #else 310 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 311 | #endif 312 | 313 | STBRP_ASSERT(node->x <= x0); 314 | 315 | min_y = 0; 316 | waste_area = 0; 317 | visited_width = 0; 318 | while (node->x < x1) { 319 | if (node->y > min_y) { 320 | // raise min_y higher. 321 | // we've accounted for all waste up to min_y, 322 | // but we'll now add more waste for everything we've visted 323 | waste_area += visited_width * (node->y - min_y); 324 | min_y = node->y; 325 | // the first time through, visited_width might be reduced 326 | if (node->x < x0) 327 | visited_width += node->next->x - x0; 328 | else 329 | visited_width += node->next->x - node->x; 330 | } else { 331 | // add waste area 332 | int under_width = node->next->x - node->x; 333 | if (under_width + visited_width > width) 334 | under_width = width - visited_width; 335 | waste_area += under_width * (min_y - node->y); 336 | visited_width += under_width; 337 | } 338 | node = node->next; 339 | } 340 | 341 | *pwaste = waste_area; 342 | return min_y; 343 | } 344 | 345 | typedef struct 346 | { 347 | int x,y; 348 | stbrp_node **prev_link; 349 | } stbrp__findresult; 350 | 351 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 352 | { 353 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 354 | stbrp__findresult fr; 355 | stbrp_node **prev, *node, *tail, **best = NULL; 356 | 357 | // align to multiple of c->align 358 | width = (width + c->align - 1); 359 | width -= width % c->align; 360 | STBRP_ASSERT(width % c->align == 0); 361 | 362 | // if it can't possibly fit, bail immediately 363 | if (width > c->width || height > c->height) { 364 | fr.prev_link = NULL; 365 | fr.x = fr.y = 0; 366 | return fr; 367 | } 368 | 369 | node = c->active_head; 370 | prev = &c->active_head; 371 | while (node->x + width <= c->width) { 372 | int y,waste; 373 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 374 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 375 | // bottom left 376 | if (y < best_y) { 377 | best_y = y; 378 | best = prev; 379 | } 380 | } else { 381 | // best-fit 382 | if (y + height <= c->height) { 383 | // can only use it if it first vertically 384 | if (y < best_y || (y == best_y && waste < best_waste)) { 385 | best_y = y; 386 | best_waste = waste; 387 | best = prev; 388 | } 389 | } 390 | } 391 | prev = &node->next; 392 | node = node->next; 393 | } 394 | 395 | best_x = (best == NULL) ? 0 : (*best)->x; 396 | 397 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 398 | // 399 | // e.g, if fitting 400 | // 401 | // ____________________ 402 | // |____________________| 403 | // 404 | // into 405 | // 406 | // | | 407 | // | ____________| 408 | // |____________| 409 | // 410 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 411 | // 412 | // This makes BF take about 2x the time 413 | 414 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 415 | tail = c->active_head; 416 | node = c->active_head; 417 | prev = &c->active_head; 418 | // find first node that's admissible 419 | while (tail->x < width) 420 | tail = tail->next; 421 | while (tail) { 422 | int xpos = tail->x - width; 423 | int y,waste; 424 | STBRP_ASSERT(xpos >= 0); 425 | // find the left position that matches this 426 | while (node->next->x <= xpos) { 427 | prev = &node->next; 428 | node = node->next; 429 | } 430 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 431 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 432 | if (y + height <= c->height) { 433 | if (y <= best_y) { 434 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 435 | best_x = xpos; 436 | STBRP_ASSERT(y <= best_y); 437 | best_y = y; 438 | best_waste = waste; 439 | best = prev; 440 | } 441 | } 442 | } 443 | tail = tail->next; 444 | } 445 | } 446 | 447 | fr.prev_link = best; 448 | fr.x = best_x; 449 | fr.y = best_y; 450 | return fr; 451 | } 452 | 453 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 454 | { 455 | // find best position according to heuristic 456 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 457 | stbrp_node *node, *cur; 458 | 459 | // bail if: 460 | // 1. it failed 461 | // 2. the best node doesn't fit (we don't always check this) 462 | // 3. we're out of memory 463 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 464 | res.prev_link = NULL; 465 | return res; 466 | } 467 | 468 | // on success, create new node 469 | node = context->free_head; 470 | node->x = (stbrp_coord) res.x; 471 | node->y = (stbrp_coord) (res.y + height); 472 | 473 | context->free_head = node->next; 474 | 475 | // insert the new node into the right starting point, and 476 | // let 'cur' point to the remaining nodes needing to be 477 | // stiched back in 478 | 479 | cur = *res.prev_link; 480 | if (cur->x < res.x) { 481 | // preserve the existing one, so start testing with the next one 482 | stbrp_node *next = cur->next; 483 | cur->next = node; 484 | cur = next; 485 | } else { 486 | *res.prev_link = node; 487 | } 488 | 489 | // from here, traverse cur and free the nodes, until we get to one 490 | // that shouldn't be freed 491 | while (cur->next && cur->next->x <= res.x + width) { 492 | stbrp_node *next = cur->next; 493 | // move the current node to the free list 494 | cur->next = context->free_head; 495 | context->free_head = cur; 496 | cur = next; 497 | } 498 | 499 | // stitch the list back in 500 | node->next = cur; 501 | 502 | if (cur->x < res.x + width) 503 | cur->x = (stbrp_coord) (res.x + width); 504 | 505 | #ifdef _DEBUG 506 | cur = context->active_head; 507 | while (cur->x < context->width) { 508 | STBRP_ASSERT(cur->x < cur->next->x); 509 | cur = cur->next; 510 | } 511 | STBRP_ASSERT(cur->next == NULL); 512 | 513 | { 514 | int count=0; 515 | cur = context->active_head; 516 | while (cur) { 517 | cur = cur->next; 518 | ++count; 519 | } 520 | cur = context->free_head; 521 | while (cur) { 522 | cur = cur->next; 523 | ++count; 524 | } 525 | STBRP_ASSERT(count == context->num_nodes+2); 526 | } 527 | #endif 528 | 529 | return res; 530 | } 531 | 532 | // [DEAR IMGUI] Added STBRP__CDECL 533 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 534 | { 535 | const stbrp_rect *p = (const stbrp_rect *) a; 536 | const stbrp_rect *q = (const stbrp_rect *) b; 537 | if (p->h > q->h) 538 | return -1; 539 | if (p->h < q->h) 540 | return 1; 541 | return (p->w > q->w) ? -1 : (p->w < q->w); 542 | } 543 | 544 | // [DEAR IMGUI] Added STBRP__CDECL 545 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 546 | { 547 | const stbrp_rect *p = (const stbrp_rect *) a; 548 | const stbrp_rect *q = (const stbrp_rect *) b; 549 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 550 | } 551 | 552 | #ifdef STBRP_LARGE_RECTS 553 | #define STBRP__MAXVAL 0xffffffff 554 | #else 555 | #define STBRP__MAXVAL 0xffff 556 | #endif 557 | 558 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 559 | { 560 | int i, all_rects_packed = 1; 561 | 562 | // we use the 'was_packed' field internally to allow sorting/unsorting 563 | for (i=0; i < num_rects; ++i) { 564 | rects[i].was_packed = i; 565 | } 566 | 567 | // sort according to heuristic 568 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 569 | 570 | for (i=0; i < num_rects; ++i) { 571 | if (rects[i].w == 0 || rects[i].h == 0) { 572 | rects[i].x = rects[i].y = 0; // empty rect needs no space 573 | } else { 574 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 575 | if (fr.prev_link) { 576 | rects[i].x = (stbrp_coord) fr.x; 577 | rects[i].y = (stbrp_coord) fr.y; 578 | } else { 579 | rects[i].x = rects[i].y = STBRP__MAXVAL; 580 | } 581 | } 582 | } 583 | 584 | // unsort 585 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 586 | 587 | // set was_packed flags and all_rects_packed status 588 | for (i=0; i < num_rects; ++i) { 589 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 590 | if (!rects[i].was_packed) 591 | all_rects_packed = 0; 592 | } 593 | 594 | // return the all_rects_packed status 595 | return all_rects_packed; 596 | } 597 | #endif 598 | 599 | /* 600 | ------------------------------------------------------------------------------ 601 | This software is available under 2 licenses -- choose whichever you prefer. 602 | ------------------------------------------------------------------------------ 603 | ALTERNATIVE A - MIT License 604 | Copyright (c) 2017 Sean Barrett 605 | Permission is hereby granted, free of charge, to any person obtaining a copy of 606 | this software and associated documentation files (the "Software"), to deal in 607 | the Software without restriction, including without limitation the rights to 608 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 609 | of the Software, and to permit persons to whom the Software is furnished to do 610 | so, subject to the following conditions: 611 | The above copyright notice and this permission notice shall be included in all 612 | copies or substantial portions of the Software. 613 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 614 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 615 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 616 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 617 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 618 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 619 | SOFTWARE. 620 | ------------------------------------------------------------------------------ 621 | ALTERNATIVE B - Public Domain (www.unlicense.org) 622 | This is free and unencumbered software released into the public domain. 623 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 624 | software, either in source code form or as a compiled binary, for any purpose, 625 | commercial or non-commercial, and by any means. 626 | In jurisdictions that recognize copyright laws, the author or authors of this 627 | software dedicate any and all copyright interest in the software to the public 628 | domain. We make this dedication for the benefit of the public at large and to 629 | the detriment of our heirs and successors. We intend this dedication to be an 630 | overt act of relinquishment in perpetuity of all present and future rights to 631 | this software under copyright law. 632 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 633 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 634 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 635 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 636 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 637 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 638 | ------------------------------------------------------------------------------ 639 | */ 640 | -------------------------------------------------------------------------------- /imgui/imstb_textedit.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_textedit.h 1.13. 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.13 - 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.13 (2019-02-07) fix bug in undo size management 39 | // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 40 | // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 41 | // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual 42 | // 1.9 (2016-08-27) customizable move-by-word 43 | // 1.8 (2016-04-02) better keyboard handling when mouse button is down 44 | // 1.7 (2015-09-13) change y range handling in case baseline is non-0 45 | // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 46 | // 1.5 (2014-09-10) add support for secondary keys for OS X 47 | // 1.4 (2014-08-17) fix signed/unsigned warnings 48 | // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 49 | // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 50 | // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 51 | // 1.0 (2012-07-26) improve documentation, initial public release 52 | // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 53 | // 0.2 (2011-11-28) fixes to undo/redo 54 | // 0.1 (2010-07-08) initial version 55 | // 56 | // ADDITIONAL CONTRIBUTORS 57 | // 58 | // Ulf Winklemann: move-by-word in 1.1 59 | // Fabian Giesen: secondary key inputs in 1.5 60 | // Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 61 | // 62 | // Bugfixes: 63 | // Scott Graham 64 | // Daniel Keller 65 | // Omar Cornut 66 | // Dan Thompson 67 | // 68 | // USAGE 69 | // 70 | // This file behaves differently depending on what symbols you define 71 | // before including it. 72 | // 73 | // 74 | // Header-file mode: 75 | // 76 | // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 77 | // it will operate in "header file" mode. In this mode, it declares a 78 | // single public symbol, STB_TexteditState, which encapsulates the current 79 | // state of a text widget (except for the string, which you will store 80 | // separately). 81 | // 82 | // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 83 | // primitive type that defines a single character (e.g. char, wchar_t, etc). 84 | // 85 | // To save space or increase undo-ability, you can optionally define the 86 | // following things that are used by the undo system: 87 | // 88 | // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 89 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 90 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 91 | // 92 | // If you don't define these, they are set to permissive types and 93 | // moderate sizes. The undo system does no memory allocations, so 94 | // it grows STB_TexteditState by the worst-case storage which is (in bytes): 95 | // 96 | // [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT 97 | // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT 98 | // 99 | // 100 | // Implementation mode: 101 | // 102 | // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 103 | // will compile the implementation of the text edit widget, depending 104 | // on a large number of symbols which must be defined before the include. 105 | // 106 | // The implementation is defined only as static functions. You will then 107 | // need to provide your own APIs in the same file which will access the 108 | // static functions. 109 | // 110 | // The basic concept is that you provide a "string" object which 111 | // behaves like an array of characters. stb_textedit uses indices to 112 | // refer to positions in the string, implicitly representing positions 113 | // in the displayed textedit. This is true for both plain text and 114 | // rich text; even with rich text stb_truetype interacts with your 115 | // code as if there was an array of all the displayed characters. 116 | // 117 | // Symbols that must be the same in header-file and implementation mode: 118 | // 119 | // STB_TEXTEDIT_CHARTYPE the character type 120 | // STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 121 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 122 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 123 | // 124 | // Symbols you must define for implementation mode: 125 | // 126 | // STB_TEXTEDIT_STRING the type of object representing a string being edited, 127 | // typically this is a wrapper object with other data you need 128 | // 129 | // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 130 | // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 131 | // starting from character #n (see discussion below) 132 | // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 133 | // to the xpos of the i+1'th char for a line of characters 134 | // starting at character #n (i.e. accounts for kerning 135 | // with previous char) 136 | // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 137 | // (return type is int, -1 means not valid to insert) 138 | // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 139 | // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 140 | // as manually wordwrapping for end-of-line positioning 141 | // 142 | // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 143 | // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 144 | // 145 | // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 146 | // 147 | // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 148 | // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 149 | // STB_TEXTEDIT_K_UP keyboard input to move cursor up 150 | // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 151 | // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 152 | // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 153 | // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 154 | // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 155 | // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 156 | // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 157 | // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 158 | // STB_TEXTEDIT_K_REDO keyboard input to perform redo 159 | // 160 | // Optional: 161 | // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 162 | // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 163 | // required for default WORDLEFT/WORDRIGHT handlers 164 | // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 165 | // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 166 | // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 167 | // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 168 | // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 169 | // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 170 | // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 171 | // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 172 | // 173 | // Todo: 174 | // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 175 | // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 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_TEXTED_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 | ///////////////////// 341 | // 342 | // private data 343 | // 344 | unsigned char cursor_at_end_of_line; // not implemented yet 345 | unsigned char initialized; 346 | unsigned char has_preferred_x; 347 | unsigned char single_line; 348 | unsigned char padding1, padding2, padding3; 349 | float preferred_x; // this determines where the cursor up/down tries to seek to along x 350 | StbUndoState undostate; 351 | } STB_TexteditState; 352 | 353 | 354 | //////////////////////////////////////////////////////////////////////// 355 | // 356 | // StbTexteditRow 357 | // 358 | // Result of layout query, used by stb_textedit to determine where 359 | // the text in each row is. 360 | 361 | // result of layout query 362 | typedef struct 363 | { 364 | float x0,x1; // starting x location, end x location (allows for align=right, etc) 365 | float baseline_y_delta; // position of baseline relative to previous row's baseline 366 | float ymin,ymax; // height of row above and below baseline 367 | int num_chars; 368 | } StbTexteditRow; 369 | #endif //INCLUDE_STB_TEXTEDIT_H 370 | 371 | 372 | //////////////////////////////////////////////////////////////////////////// 373 | //////////////////////////////////////////////////////////////////////////// 374 | //// 375 | //// Implementation mode 376 | //// 377 | //// 378 | 379 | 380 | // implementation isn't include-guarded, since it might have indirectly 381 | // included just the "header" portion 382 | #ifdef STB_TEXTEDIT_IMPLEMENTATION 383 | 384 | #ifndef STB_TEXTEDIT_memmove 385 | #include 386 | #define STB_TEXTEDIT_memmove memmove 387 | #endif 388 | 389 | 390 | ///////////////////////////////////////////////////////////////////////////// 391 | // 392 | // Mouse input handling 393 | // 394 | 395 | // traverse the layout to locate the nearest character to a display position 396 | static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) 397 | { 398 | StbTexteditRow r; 399 | int n = STB_TEXTEDIT_STRINGLEN(str); 400 | float base_y = 0, prev_x; 401 | int i=0, k; 402 | 403 | r.x0 = r.x1 = 0; 404 | r.ymin = r.ymax = 0; 405 | r.num_chars = 0; 406 | 407 | // search rows to find one that straddles 'y' 408 | while (i < n) { 409 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 410 | if (r.num_chars <= 0) 411 | return n; 412 | 413 | if (i==0 && y < base_y + r.ymin) 414 | return 0; 415 | 416 | if (y < base_y + r.ymax) 417 | break; 418 | 419 | i += r.num_chars; 420 | base_y += r.baseline_y_delta; 421 | } 422 | 423 | // below all text, return 'after' last character 424 | if (i >= n) 425 | return n; 426 | 427 | // check if it's before the beginning of the line 428 | if (x < r.x0) 429 | return i; 430 | 431 | // check if it's before the end of the line 432 | if (x < r.x1) { 433 | // search characters in row for one that straddles 'x' 434 | prev_x = r.x0; 435 | for (k=0; k < r.num_chars; ++k) { 436 | float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 437 | if (x < prev_x+w) { 438 | if (x < prev_x+w/2) 439 | return k+i; 440 | else 441 | return k+i+1; 442 | } 443 | prev_x += w; 444 | } 445 | // shouldn't happen, but if it does, fall through to end-of-line case 446 | } 447 | 448 | // if the last character is a newline, return that. otherwise return 'after' the last character 449 | if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 450 | return i+r.num_chars-1; 451 | else 452 | return i+r.num_chars; 453 | } 454 | 455 | // API click: on mouse down, move the cursor to the clicked location, and reset the selection 456 | static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 457 | { 458 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 459 | // goes off the top or bottom of the text 460 | if( state->single_line ) 461 | { 462 | StbTexteditRow r; 463 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 464 | y = r.ymin; 465 | } 466 | 467 | state->cursor = stb_text_locate_coord(str, x, y); 468 | state->select_start = state->cursor; 469 | state->select_end = state->cursor; 470 | state->has_preferred_x = 0; 471 | } 472 | 473 | // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 474 | static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 475 | { 476 | int p = 0; 477 | 478 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 479 | // goes off the top or bottom of the text 480 | if( state->single_line ) 481 | { 482 | StbTexteditRow r; 483 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 484 | y = r.ymin; 485 | } 486 | 487 | if (state->select_start == state->select_end) 488 | state->select_start = state->cursor; 489 | 490 | p = stb_text_locate_coord(str, x, y); 491 | state->cursor = state->select_end = p; 492 | } 493 | 494 | ///////////////////////////////////////////////////////////////////////////// 495 | // 496 | // Keyboard input handling 497 | // 498 | 499 | // forward declarations 500 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 501 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 502 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 503 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 504 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 505 | 506 | typedef struct 507 | { 508 | float x,y; // position of n'th character 509 | float height; // height of line 510 | int first_char, length; // first char of row, and length 511 | int prev_first; // first char of previous row 512 | } StbFindState; 513 | 514 | // find the x/y location of a character, and remember info about the previous row in 515 | // case we get a move-up event (for page up, we'll have to rescan) 516 | static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) 517 | { 518 | StbTexteditRow r; 519 | int prev_start = 0; 520 | int z = STB_TEXTEDIT_STRINGLEN(str); 521 | int i=0, first; 522 | 523 | if (n == z) { 524 | // if it's at the end, then find the last line -- simpler than trying to 525 | // explicitly handle this case in the regular code 526 | if (single_line) { 527 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 528 | find->y = 0; 529 | find->first_char = 0; 530 | find->length = z; 531 | find->height = r.ymax - r.ymin; 532 | find->x = r.x1; 533 | } else { 534 | find->y = 0; 535 | find->x = 0; 536 | find->height = 1; 537 | while (i < z) { 538 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 539 | prev_start = i; 540 | i += r.num_chars; 541 | } 542 | find->first_char = i; 543 | find->length = 0; 544 | find->prev_first = prev_start; 545 | } 546 | return; 547 | } 548 | 549 | // search rows to find the one that straddles character n 550 | find->y = 0; 551 | 552 | for(;;) { 553 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 554 | if (n < i + r.num_chars) 555 | break; 556 | prev_start = i; 557 | i += r.num_chars; 558 | find->y += r.baseline_y_delta; 559 | } 560 | 561 | find->first_char = first = i; 562 | find->length = r.num_chars; 563 | find->height = r.ymax - r.ymin; 564 | find->prev_first = prev_start; 565 | 566 | // now scan to find xpos 567 | find->x = r.x0; 568 | for (i=0; first+i < n; ++i) 569 | find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 570 | } 571 | 572 | #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 573 | 574 | // make the selection/cursor state valid if client altered the string 575 | static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 576 | { 577 | int n = STB_TEXTEDIT_STRINGLEN(str); 578 | if (STB_TEXT_HAS_SELECTION(state)) { 579 | if (state->select_start > n) state->select_start = n; 580 | if (state->select_end > n) state->select_end = n; 581 | // if clamping forced them to be equal, move the cursor to match 582 | if (state->select_start == state->select_end) 583 | state->cursor = state->select_start; 584 | } 585 | if (state->cursor > n) state->cursor = n; 586 | } 587 | 588 | // delete characters while updating undo 589 | static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 590 | { 591 | stb_text_makeundo_delete(str, state, where, len); 592 | STB_TEXTEDIT_DELETECHARS(str, where, len); 593 | state->has_preferred_x = 0; 594 | } 595 | 596 | // delete the section 597 | static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 598 | { 599 | stb_textedit_clamp(str, state); 600 | if (STB_TEXT_HAS_SELECTION(state)) { 601 | if (state->select_start < state->select_end) { 602 | stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 603 | state->select_end = state->cursor = state->select_start; 604 | } else { 605 | stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 606 | state->select_start = state->cursor = state->select_end; 607 | } 608 | state->has_preferred_x = 0; 609 | } 610 | } 611 | 612 | // canoncialize the selection so start <= end 613 | static void stb_textedit_sortselection(STB_TexteditState *state) 614 | { 615 | if (state->select_end < state->select_start) { 616 | int temp = state->select_end; 617 | state->select_end = state->select_start; 618 | state->select_start = temp; 619 | } 620 | } 621 | 622 | // move cursor to first character of selection 623 | static void stb_textedit_move_to_first(STB_TexteditState *state) 624 | { 625 | if (STB_TEXT_HAS_SELECTION(state)) { 626 | stb_textedit_sortselection(state); 627 | state->cursor = state->select_start; 628 | state->select_end = state->select_start; 629 | state->has_preferred_x = 0; 630 | } 631 | } 632 | 633 | // move cursor to last character of selection 634 | static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 635 | { 636 | if (STB_TEXT_HAS_SELECTION(state)) { 637 | stb_textedit_sortselection(state); 638 | stb_textedit_clamp(str, state); 639 | state->cursor = state->select_end; 640 | state->select_start = state->select_end; 641 | state->has_preferred_x = 0; 642 | } 643 | } 644 | 645 | #ifdef STB_TEXTEDIT_IS_SPACE 646 | static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) 647 | { 648 | return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 649 | } 650 | 651 | #ifndef STB_TEXTEDIT_MOVEWORDLEFT 652 | static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) 653 | { 654 | --c; // always move at least one character 655 | while( c >= 0 && !is_word_boundary( str, c ) ) 656 | --c; 657 | 658 | if( c < 0 ) 659 | c = 0; 660 | 661 | return c; 662 | } 663 | #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 664 | #endif 665 | 666 | #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 667 | static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) 668 | { 669 | const int len = STB_TEXTEDIT_STRINGLEN(str); 670 | ++c; // always move at least one character 671 | while( c < len && !is_word_boundary( str, c ) ) 672 | ++c; 673 | 674 | if( c > len ) 675 | c = len; 676 | 677 | return c; 678 | } 679 | #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 680 | #endif 681 | 682 | #endif 683 | 684 | // update selection and cursor to match each other 685 | static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 686 | { 687 | if (!STB_TEXT_HAS_SELECTION(state)) 688 | state->select_start = state->select_end = state->cursor; 689 | else 690 | state->cursor = state->select_end; 691 | } 692 | 693 | // API cut: delete selection 694 | static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 695 | { 696 | if (STB_TEXT_HAS_SELECTION(state)) { 697 | stb_textedit_delete_selection(str,state); // implicitly clamps 698 | state->has_preferred_x = 0; 699 | return 1; 700 | } 701 | return 0; 702 | } 703 | 704 | // API paste: replace existing selection with passed-in text 705 | static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 706 | { 707 | // if there's a selection, the paste should delete it 708 | stb_textedit_clamp(str, state); 709 | stb_textedit_delete_selection(str,state); 710 | // try to insert the characters 711 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { 712 | stb_text_makeundo_insert(state, state->cursor, len); 713 | state->cursor += len; 714 | state->has_preferred_x = 0; 715 | return 1; 716 | } 717 | // remove the undo since we didn't actually insert the characters 718 | if (state->undostate.undo_point) 719 | --state->undostate.undo_point; 720 | return 0; 721 | } 722 | 723 | #ifndef STB_TEXTEDIT_KEYTYPE 724 | #define STB_TEXTEDIT_KEYTYPE int 725 | #endif 726 | 727 | // API key: process a keyboard input 728 | static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 729 | { 730 | retry: 731 | switch (key) { 732 | default: { 733 | int c = STB_TEXTEDIT_KEYTOTEXT(key); 734 | if (c > 0) { 735 | STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; 736 | 737 | // can't add newline in single-line mode 738 | if (c == '\n' && state->single_line) 739 | break; 740 | 741 | if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 742 | stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 743 | STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 744 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 745 | ++state->cursor; 746 | state->has_preferred_x = 0; 747 | } 748 | } else { 749 | stb_textedit_delete_selection(str,state); // implicitly clamps 750 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 751 | stb_text_makeundo_insert(state, state->cursor, 1); 752 | ++state->cursor; 753 | state->has_preferred_x = 0; 754 | } 755 | } 756 | } 757 | break; 758 | } 759 | 760 | #ifdef STB_TEXTEDIT_K_INSERT 761 | case STB_TEXTEDIT_K_INSERT: 762 | state->insert_mode = !state->insert_mode; 763 | break; 764 | #endif 765 | 766 | case STB_TEXTEDIT_K_UNDO: 767 | stb_text_undo(str, state); 768 | state->has_preferred_x = 0; 769 | break; 770 | 771 | case STB_TEXTEDIT_K_REDO: 772 | stb_text_redo(str, state); 773 | state->has_preferred_x = 0; 774 | break; 775 | 776 | case STB_TEXTEDIT_K_LEFT: 777 | // if currently there's a selection, move cursor to start of selection 778 | if (STB_TEXT_HAS_SELECTION(state)) 779 | stb_textedit_move_to_first(state); 780 | else 781 | if (state->cursor > 0) 782 | --state->cursor; 783 | state->has_preferred_x = 0; 784 | break; 785 | 786 | case STB_TEXTEDIT_K_RIGHT: 787 | // if currently there's a selection, move cursor to end of selection 788 | if (STB_TEXT_HAS_SELECTION(state)) 789 | stb_textedit_move_to_last(str, state); 790 | else 791 | ++state->cursor; 792 | stb_textedit_clamp(str, state); 793 | state->has_preferred_x = 0; 794 | break; 795 | 796 | case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 797 | stb_textedit_clamp(str, state); 798 | stb_textedit_prep_selection_at_cursor(state); 799 | // move selection left 800 | if (state->select_end > 0) 801 | --state->select_end; 802 | state->cursor = state->select_end; 803 | state->has_preferred_x = 0; 804 | break; 805 | 806 | #ifdef STB_TEXTEDIT_MOVEWORDLEFT 807 | case STB_TEXTEDIT_K_WORDLEFT: 808 | if (STB_TEXT_HAS_SELECTION(state)) 809 | stb_textedit_move_to_first(state); 810 | else { 811 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 812 | stb_textedit_clamp( str, state ); 813 | } 814 | break; 815 | 816 | case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 817 | if( !STB_TEXT_HAS_SELECTION( state ) ) 818 | stb_textedit_prep_selection_at_cursor(state); 819 | 820 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 821 | state->select_end = state->cursor; 822 | 823 | stb_textedit_clamp( str, state ); 824 | break; 825 | #endif 826 | 827 | #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 828 | case STB_TEXTEDIT_K_WORDRIGHT: 829 | if (STB_TEXT_HAS_SELECTION(state)) 830 | stb_textedit_move_to_last(str, state); 831 | else { 832 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 833 | stb_textedit_clamp( str, state ); 834 | } 835 | break; 836 | 837 | case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 838 | if( !STB_TEXT_HAS_SELECTION( state ) ) 839 | stb_textedit_prep_selection_at_cursor(state); 840 | 841 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 842 | state->select_end = state->cursor; 843 | 844 | stb_textedit_clamp( str, state ); 845 | break; 846 | #endif 847 | 848 | case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 849 | stb_textedit_prep_selection_at_cursor(state); 850 | // move selection right 851 | ++state->select_end; 852 | stb_textedit_clamp(str, state); 853 | state->cursor = state->select_end; 854 | state->has_preferred_x = 0; 855 | break; 856 | 857 | case STB_TEXTEDIT_K_DOWN: 858 | case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { 859 | StbFindState find; 860 | StbTexteditRow row; 861 | int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 862 | 863 | if (state->single_line) { 864 | // on windows, up&down in single-line behave like left&right 865 | key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 866 | goto retry; 867 | } 868 | 869 | if (sel) 870 | stb_textedit_prep_selection_at_cursor(state); 871 | else if (STB_TEXT_HAS_SELECTION(state)) 872 | stb_textedit_move_to_last(str,state); 873 | 874 | // compute current position of cursor point 875 | stb_textedit_clamp(str, state); 876 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 877 | 878 | // now find character position down a row 879 | if (find.length) { 880 | float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 881 | float x; 882 | int start = find.first_char + find.length; 883 | state->cursor = start; 884 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 885 | x = row.x0; 886 | for (i=0; i < row.num_chars; ++i) { 887 | float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 888 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 889 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 890 | break; 891 | #endif 892 | x += dx; 893 | if (x > goal_x) 894 | break; 895 | ++state->cursor; 896 | } 897 | stb_textedit_clamp(str, state); 898 | 899 | state->has_preferred_x = 1; 900 | state->preferred_x = goal_x; 901 | 902 | if (sel) 903 | state->select_end = state->cursor; 904 | } 905 | break; 906 | } 907 | 908 | case STB_TEXTEDIT_K_UP: 909 | case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { 910 | StbFindState find; 911 | StbTexteditRow row; 912 | int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 913 | 914 | if (state->single_line) { 915 | // on windows, up&down become left&right 916 | key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 917 | goto retry; 918 | } 919 | 920 | if (sel) 921 | stb_textedit_prep_selection_at_cursor(state); 922 | else if (STB_TEXT_HAS_SELECTION(state)) 923 | stb_textedit_move_to_first(state); 924 | 925 | // compute current position of cursor point 926 | stb_textedit_clamp(str, state); 927 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 928 | 929 | // can only go up if there's a previous row 930 | if (find.prev_first != find.first_char) { 931 | // now find character position up a row 932 | float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 933 | float x; 934 | state->cursor = find.prev_first; 935 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 936 | x = row.x0; 937 | for (i=0; i < row.num_chars; ++i) { 938 | float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 939 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 940 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 941 | break; 942 | #endif 943 | x += dx; 944 | if (x > goal_x) 945 | break; 946 | ++state->cursor; 947 | } 948 | stb_textedit_clamp(str, state); 949 | 950 | state->has_preferred_x = 1; 951 | state->preferred_x = goal_x; 952 | 953 | if (sel) 954 | state->select_end = state->cursor; 955 | } 956 | break; 957 | } 958 | 959 | case STB_TEXTEDIT_K_DELETE: 960 | case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 961 | if (STB_TEXT_HAS_SELECTION(state)) 962 | stb_textedit_delete_selection(str, state); 963 | else { 964 | int n = STB_TEXTEDIT_STRINGLEN(str); 965 | if (state->cursor < n) 966 | stb_textedit_delete(str, state, state->cursor, 1); 967 | } 968 | state->has_preferred_x = 0; 969 | break; 970 | 971 | case STB_TEXTEDIT_K_BACKSPACE: 972 | case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 973 | if (STB_TEXT_HAS_SELECTION(state)) 974 | stb_textedit_delete_selection(str, state); 975 | else { 976 | stb_textedit_clamp(str, state); 977 | if (state->cursor > 0) { 978 | stb_textedit_delete(str, state, state->cursor-1, 1); 979 | --state->cursor; 980 | } 981 | } 982 | state->has_preferred_x = 0; 983 | break; 984 | 985 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 986 | case STB_TEXTEDIT_K_TEXTSTART2: 987 | #endif 988 | case STB_TEXTEDIT_K_TEXTSTART: 989 | state->cursor = state->select_start = state->select_end = 0; 990 | state->has_preferred_x = 0; 991 | break; 992 | 993 | #ifdef STB_TEXTEDIT_K_TEXTEND2 994 | case STB_TEXTEDIT_K_TEXTEND2: 995 | #endif 996 | case STB_TEXTEDIT_K_TEXTEND: 997 | state->cursor = STB_TEXTEDIT_STRINGLEN(str); 998 | state->select_start = state->select_end = 0; 999 | state->has_preferred_x = 0; 1000 | break; 1001 | 1002 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1003 | case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 1004 | #endif 1005 | case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 1006 | stb_textedit_prep_selection_at_cursor(state); 1007 | state->cursor = state->select_end = 0; 1008 | state->has_preferred_x = 0; 1009 | break; 1010 | 1011 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1012 | case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 1013 | #endif 1014 | case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 1015 | stb_textedit_prep_selection_at_cursor(state); 1016 | state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 1017 | state->has_preferred_x = 0; 1018 | break; 1019 | 1020 | 1021 | #ifdef STB_TEXTEDIT_K_LINESTART2 1022 | case STB_TEXTEDIT_K_LINESTART2: 1023 | #endif 1024 | case STB_TEXTEDIT_K_LINESTART: 1025 | stb_textedit_clamp(str, state); 1026 | stb_textedit_move_to_first(state); 1027 | if (state->single_line) 1028 | state->cursor = 0; 1029 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1030 | --state->cursor; 1031 | state->has_preferred_x = 0; 1032 | break; 1033 | 1034 | #ifdef STB_TEXTEDIT_K_LINEEND2 1035 | case STB_TEXTEDIT_K_LINEEND2: 1036 | #endif 1037 | case STB_TEXTEDIT_K_LINEEND: { 1038 | int n = STB_TEXTEDIT_STRINGLEN(str); 1039 | stb_textedit_clamp(str, state); 1040 | stb_textedit_move_to_first(state); 1041 | if (state->single_line) 1042 | state->cursor = n; 1043 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1044 | ++state->cursor; 1045 | state->has_preferred_x = 0; 1046 | break; 1047 | } 1048 | 1049 | #ifdef STB_TEXTEDIT_K_LINESTART2 1050 | case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1051 | #endif 1052 | case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1053 | stb_textedit_clamp(str, state); 1054 | stb_textedit_prep_selection_at_cursor(state); 1055 | if (state->single_line) 1056 | state->cursor = 0; 1057 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1058 | --state->cursor; 1059 | state->select_end = state->cursor; 1060 | state->has_preferred_x = 0; 1061 | break; 1062 | 1063 | #ifdef STB_TEXTEDIT_K_LINEEND2 1064 | case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1065 | #endif 1066 | case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1067 | int n = STB_TEXTEDIT_STRINGLEN(str); 1068 | stb_textedit_clamp(str, state); 1069 | stb_textedit_prep_selection_at_cursor(state); 1070 | if (state->single_line) 1071 | state->cursor = n; 1072 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1073 | ++state->cursor; 1074 | state->select_end = state->cursor; 1075 | state->has_preferred_x = 0; 1076 | break; 1077 | } 1078 | 1079 | // @TODO: 1080 | // STB_TEXTEDIT_K_PGUP - move cursor up a page 1081 | // STB_TEXTEDIT_K_PGDOWN - move cursor down a page 1082 | } 1083 | } 1084 | 1085 | ///////////////////////////////////////////////////////////////////////////// 1086 | // 1087 | // Undo processing 1088 | // 1089 | // @OPTIMIZE: the undo/redo buffer should be circular 1090 | 1091 | static void stb_textedit_flush_redo(StbUndoState *state) 1092 | { 1093 | state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1094 | state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1095 | } 1096 | 1097 | // discard the oldest entry in the undo list 1098 | static void stb_textedit_discard_undo(StbUndoState *state) 1099 | { 1100 | if (state->undo_point > 0) { 1101 | // if the 0th undo state has characters, clean those up 1102 | if (state->undo_rec[0].char_storage >= 0) { 1103 | int n = state->undo_rec[0].insert_length, i; 1104 | // delete n characters from all other records 1105 | state->undo_char_point -= n; 1106 | STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); 1107 | for (i=0; i < state->undo_point; ++i) 1108 | if (state->undo_rec[i].char_storage >= 0) 1109 | state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 1110 | } 1111 | --state->undo_point; 1112 | STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 1113 | } 1114 | } 1115 | 1116 | // discard the oldest entry in the redo list--it's bad if this 1117 | // ever happens, but because undo & redo have to store the actual 1118 | // characters in different cases, the redo character buffer can 1119 | // fill up even though the undo buffer didn't 1120 | static void stb_textedit_discard_redo(StbUndoState *state) 1121 | { 1122 | int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; 1123 | 1124 | if (state->redo_point <= k) { 1125 | // if the k'th undo state has characters, clean those up 1126 | if (state->undo_rec[k].char_storage >= 0) { 1127 | int n = state->undo_rec[k].insert_length, i; 1128 | // move the remaining redo character data to the end of the buffer 1129 | state->redo_char_point += n; 1130 | 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))); 1131 | // adjust the position of all the other records to account for above memmove 1132 | for (i=state->redo_point; i < k; ++i) 1133 | if (state->undo_rec[i].char_storage >= 0) 1134 | state->undo_rec[i].char_storage += n; 1135 | } 1136 | // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 1137 | // {DEAR IMGUI] 1138 | size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 1139 | const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 1140 | const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 1141 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 1142 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 1143 | STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 1144 | 1145 | // now move redo_point to point to the new one 1146 | ++state->redo_point; 1147 | } 1148 | } 1149 | 1150 | static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1151 | { 1152 | // any time we create a new undo record, we discard redo 1153 | stb_textedit_flush_redo(state); 1154 | 1155 | // if we have no free records, we have to make room, by sliding the 1156 | // existing records down 1157 | if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1158 | stb_textedit_discard_undo(state); 1159 | 1160 | // if the characters to store won't possibly fit in the buffer, we can't undo 1161 | if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { 1162 | state->undo_point = 0; 1163 | state->undo_char_point = 0; 1164 | return NULL; 1165 | } 1166 | 1167 | // if we don't have enough free characters in the buffer, we have to make room 1168 | while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) 1169 | stb_textedit_discard_undo(state); 1170 | 1171 | return &state->undo_rec[state->undo_point++]; 1172 | } 1173 | 1174 | static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1175 | { 1176 | StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1177 | if (r == NULL) 1178 | return NULL; 1179 | 1180 | r->where = pos; 1181 | r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; 1182 | r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; 1183 | 1184 | if (insert_len == 0) { 1185 | r->char_storage = -1; 1186 | return NULL; 1187 | } else { 1188 | r->char_storage = state->undo_char_point; 1189 | state->undo_char_point += insert_len; 1190 | return &state->undo_char[r->char_storage]; 1191 | } 1192 | } 1193 | 1194 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1195 | { 1196 | StbUndoState *s = &state->undostate; 1197 | StbUndoRecord u, *r; 1198 | if (s->undo_point == 0) 1199 | return; 1200 | 1201 | // we need to do two things: apply the undo record, and create a redo record 1202 | u = s->undo_rec[s->undo_point-1]; 1203 | r = &s->undo_rec[s->redo_point-1]; 1204 | r->char_storage = -1; 1205 | 1206 | r->insert_length = u.delete_length; 1207 | r->delete_length = u.insert_length; 1208 | r->where = u.where; 1209 | 1210 | if (u.delete_length) { 1211 | // if the undo record says to delete characters, then the redo record will 1212 | // need to re-insert the characters that get deleted, so we need to store 1213 | // them. 1214 | 1215 | // there are three cases: 1216 | // there's enough room to store the characters 1217 | // characters stored for *redoing* don't leave room for redo 1218 | // characters stored for *undoing* don't leave room for redo 1219 | // if the last is true, we have to bail 1220 | 1221 | if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { 1222 | // the undo records take up too much character space; there's no space to store the redo characters 1223 | r->insert_length = 0; 1224 | } else { 1225 | int i; 1226 | 1227 | // there's definitely room to store the characters eventually 1228 | while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1229 | // should never happen: 1230 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1231 | return; 1232 | // there's currently not enough room, so discard a redo record 1233 | stb_textedit_discard_redo(s); 1234 | } 1235 | r = &s->undo_rec[s->redo_point-1]; 1236 | 1237 | r->char_storage = s->redo_char_point - u.delete_length; 1238 | s->redo_char_point = s->redo_char_point - u.delete_length; 1239 | 1240 | // now save the characters 1241 | for (i=0; i < u.delete_length; ++i) 1242 | s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1243 | } 1244 | 1245 | // now we can carry out the deletion 1246 | STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1247 | } 1248 | 1249 | // check type of recorded action: 1250 | if (u.insert_length) { 1251 | // easy case: was a deletion, so we need to insert n characters 1252 | STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1253 | s->undo_char_point -= u.insert_length; 1254 | } 1255 | 1256 | state->cursor = u.where + u.insert_length; 1257 | 1258 | s->undo_point--; 1259 | s->redo_point--; 1260 | } 1261 | 1262 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1263 | { 1264 | StbUndoState *s = &state->undostate; 1265 | StbUndoRecord *u, r; 1266 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1267 | return; 1268 | 1269 | // we need to do two things: apply the redo record, and create an undo record 1270 | u = &s->undo_rec[s->undo_point]; 1271 | r = s->undo_rec[s->redo_point]; 1272 | 1273 | // we KNOW there must be room for the undo record, because the redo record 1274 | // was derived from an undo record 1275 | 1276 | u->delete_length = r.insert_length; 1277 | u->insert_length = r.delete_length; 1278 | u->where = r.where; 1279 | u->char_storage = -1; 1280 | 1281 | if (r.delete_length) { 1282 | // the redo record requires us to delete characters, so the undo record 1283 | // needs to store the characters 1284 | 1285 | if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1286 | u->insert_length = 0; 1287 | u->delete_length = 0; 1288 | } else { 1289 | int i; 1290 | u->char_storage = s->undo_char_point; 1291 | s->undo_char_point = s->undo_char_point + u->insert_length; 1292 | 1293 | // now save the characters 1294 | for (i=0; i < u->insert_length; ++i) 1295 | s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1296 | } 1297 | 1298 | STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1299 | } 1300 | 1301 | if (r.insert_length) { 1302 | // easy case: need to insert n characters 1303 | STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1304 | s->redo_char_point += r.insert_length; 1305 | } 1306 | 1307 | state->cursor = r.where + r.insert_length; 1308 | 1309 | s->undo_point++; 1310 | s->redo_point++; 1311 | } 1312 | 1313 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1314 | { 1315 | stb_text_createundo(&state->undostate, where, 0, length); 1316 | } 1317 | 1318 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1319 | { 1320 | int i; 1321 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1322 | if (p) { 1323 | for (i=0; i < length; ++i) 1324 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1325 | } 1326 | } 1327 | 1328 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1329 | { 1330 | int i; 1331 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1332 | if (p) { 1333 | for (i=0; i < old_length; ++i) 1334 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1335 | } 1336 | } 1337 | 1338 | // reset the state to default 1339 | static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1340 | { 1341 | state->undostate.undo_point = 0; 1342 | state->undostate.undo_char_point = 0; 1343 | state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1344 | state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1345 | state->select_end = state->select_start = 0; 1346 | state->cursor = 0; 1347 | state->has_preferred_x = 0; 1348 | state->preferred_x = 0; 1349 | state->cursor_at_end_of_line = 0; 1350 | state->initialized = 1; 1351 | state->single_line = (unsigned char) is_single_line; 1352 | state->insert_mode = 0; 1353 | } 1354 | 1355 | // API initialize 1356 | static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1357 | { 1358 | stb_textedit_clear_state(state, is_single_line); 1359 | } 1360 | 1361 | #if defined(__GNUC__) || defined(__clang__) 1362 | #pragma GCC diagnostic push 1363 | #pragma GCC diagnostic ignored "-Wcast-qual" 1364 | #endif 1365 | 1366 | static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) 1367 | { 1368 | return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); 1369 | } 1370 | 1371 | #if defined(__GNUC__) || defined(__clang__) 1372 | #pragma GCC diagnostic pop 1373 | #endif 1374 | 1375 | #endif//STB_TEXTEDIT_IMPLEMENTATION 1376 | 1377 | /* 1378 | ------------------------------------------------------------------------------ 1379 | This software is available under 2 licenses -- choose whichever you prefer. 1380 | ------------------------------------------------------------------------------ 1381 | ALTERNATIVE A - MIT License 1382 | Copyright (c) 2017 Sean Barrett 1383 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1384 | this software and associated documentation files (the "Software"), to deal in 1385 | the Software without restriction, including without limitation the rights to 1386 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1387 | of the Software, and to permit persons to whom the Software is furnished to do 1388 | so, subject to the following conditions: 1389 | The above copyright notice and this permission notice shall be included in all 1390 | copies or substantial portions of the Software. 1391 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1392 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1393 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1394 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1395 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1396 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1397 | SOFTWARE. 1398 | ------------------------------------------------------------------------------ 1399 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1400 | This is free and unencumbered software released into the public domain. 1401 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1402 | software, either in source code form or as a compiled binary, for any purpose, 1403 | commercial or non-commercial, and by any means. 1404 | In jurisdictions that recognize copyright laws, the author or authors of this 1405 | software dedicate any and all copyright interest in the software to the public 1406 | domain. We make this dedication for the benefit of the public at large and to 1407 | the detriment of our heirs and successors. We intend this dedication to be an 1408 | overt act of relinquishment in perpetuity of all present and future rights to 1409 | this software under copyright law. 1410 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1411 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1412 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1413 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1414 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1415 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1416 | ------------------------------------------------------------------------------ 1417 | */ 1418 | -------------------------------------------------------------------------------- /imnodes/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Johann Muszynski 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. -------------------------------------------------------------------------------- /imnodes/imnodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(__GNUC__) || defined(__clang__) 6 | #define DEPRECATED __attribute__((deprecated)) 7 | #elif defined(_MSC_VER) 8 | #define DEPRECATED __declspec(deprecated) 9 | #else 10 | #pragma message("WARNING: You need to implement DEPRECATED for this compiler") 11 | #define DEPRECATED 12 | #endif 13 | 14 | struct ImVec2; 15 | 16 | namespace imnodes 17 | { 18 | enum ColorStyle 19 | { 20 | ColorStyle_NodeBackground = 0, 21 | ColorStyle_NodeBackgroundHovered, 22 | ColorStyle_NodeBackgroundSelected, 23 | ColorStyle_NodeOutline, 24 | ColorStyle_TitleBar, 25 | ColorStyle_TitleBarHovered, 26 | ColorStyle_TitleBarSelected, 27 | ColorStyle_Link, 28 | ColorStyle_LinkHovered, 29 | ColorStyle_LinkSelected, 30 | ColorStyle_Pin, 31 | ColorStyle_PinHovered, 32 | ColorStyle_BoxSelector, 33 | ColorStyle_BoxSelectorOutline, 34 | ColorStyle_GridBackground, 35 | ColorStyle_GridLine, 36 | ColorStyle_Count 37 | }; 38 | 39 | enum StyleVar 40 | { 41 | StyleVar_GridSpacing = 0, 42 | StyleVar_NodeCornerRounding, 43 | StyleVar_NodePaddingHorizontal, 44 | StyleVar_NodePaddingVertical 45 | }; 46 | 47 | enum StyleFlags 48 | { 49 | StyleFlags_None = 0, 50 | StyleFlags_NodeOutline = 1 << 0, 51 | StyleFlags_GridLines = 1 << 2 52 | }; 53 | 54 | // This enum controls the way attribute pins look. 55 | enum PinShape 56 | { 57 | PinShape_Circle, 58 | PinShape_CircleFilled, 59 | PinShape_Triangle, 60 | PinShape_TriangleFilled, 61 | PinShape_Quad, 62 | PinShape_QuadFilled 63 | }; 64 | 65 | // This enum controls the way the attribute pins behave. 66 | enum AttributeFlags 67 | { 68 | AttributeFlags_None = 0, 69 | // Allow detaching a link by left-clicking and dragging the link at a pin it 70 | // is connected to. NOTE: the user has to actually delete the link for this 71 | // to work. A deleted link can be detected by calling IsLinkDestroyed() 72 | // after EndNodeEditor(). 73 | AttributeFlags_EnableLinkDetachWithDragClick = 1 << 0 74 | }; 75 | 76 | struct IO 77 | { 78 | struct EmulateThreeButtonMouse 79 | { 80 | EmulateThreeButtonMouse(); 81 | 82 | // Controls whether this feature is enabled or not. 83 | bool enabled; 84 | const bool* modifier; // The keyboard modifier to use with the mouse 85 | // left click. Set to &ImGuiIO::KeyAlt by default. 86 | } emulate_three_button_mouse; 87 | 88 | struct LinkDetachWithModifierClick 89 | { 90 | LinkDetachWithModifierClick(); 91 | 92 | // Pointer to a boolean value indicating when the desired modifier is 93 | // pressed. Set to NULL by default (i.e. this feature is disabled). To 94 | // enable the feature, set the link to point to, for example, 95 | // &ImGuiIO::KeyCtrl. 96 | // 97 | // Left-clicking a link with this modifier pressed will detach that 98 | // link. NOTE: the user has to actually delete the link for this to 99 | // work. A deleted link can be detected by calling IsLinkDestroyed() 100 | // after EndNodeEditor(). 101 | const bool* modifier; 102 | } link_detach_with_modifier_click; 103 | 104 | IO(); 105 | }; 106 | 107 | struct Style 108 | { 109 | float grid_spacing; 110 | 111 | float node_corner_rounding; 112 | float node_padding_horizontal; 113 | float node_padding_vertical; 114 | 115 | float link_thickness; 116 | float link_line_segments_per_length; 117 | float link_hover_distance; 118 | 119 | // The following variables control the look and behavior of the pins. The 120 | // default size of each pin shape is balanced to occupy approximately the 121 | // same surface area on the screen. 122 | 123 | // The circle radius used when the pin shape is either PinShape_Circle or 124 | // PinShape_CircleFilled. 125 | float pin_circle_radius; 126 | // The quad side length used when the shape is either PinShape_Quad or 127 | // PinShape_QuadFilled. 128 | float pin_quad_side_length; 129 | // The equilateral triangle side length used when the pin shape is either 130 | // PinShape_Triangle or PinShape_TriangleFilled. 131 | float pin_triangle_side_length; 132 | // The thickness of the line used when the pin shape is not filled. 133 | float pin_line_thickness; 134 | // The radius from the pin's center position inside of which it is detected 135 | // as being hovered over. 136 | float pin_hover_radius; 137 | // Offsets the pins' positions from the edge of the node to the outside of 138 | // the node. 139 | float pin_offset; 140 | 141 | // By default, StyleFlags_NodeOutline and StyleFlags_Gridlines are enabled. 142 | StyleFlags flags; 143 | // Set these mid-frame using Push/PopColorStyle. You can index this color 144 | // array with with a ColorStyle enum value. 145 | unsigned int colors[ColorStyle_Count]; 146 | 147 | Style(); 148 | }; 149 | 150 | // An editor context corresponds to a set of nodes in a single workspace 151 | // (created with a single Begin/EndNodeEditor pair) 152 | // 153 | // By default, the library creates an editor context behind the scenes, so 154 | // using any of the imnodes functions doesn't require you to explicitly create a 155 | // context. 156 | struct EditorContext; 157 | 158 | EditorContext* EditorContextCreate(); 159 | void EditorContextFree(EditorContext*); 160 | void EditorContextSet(EditorContext*); 161 | ImVec2 EditorContextGetPanning(); 162 | void EditorContextResetPanning(const ImVec2& pos); 163 | void EditorContextMoveToNode(const int node_id); 164 | 165 | // Initialize the node editor system. 166 | void Initialize(); 167 | void Shutdown(); 168 | 169 | IO& GetIO(); 170 | 171 | // Returns the global style struct. See the struct declaration for default 172 | // values. 173 | Style& GetStyle(); 174 | // Style presets matching the dear imgui styles of the same name. 175 | void StyleColorsDark(); // on by default 176 | void StyleColorsClassic(); 177 | void StyleColorsLight(); 178 | 179 | // The top-level function call. Call this before calling BeginNode/EndNode. 180 | // Calling this function will result the node editor grid workspace being 181 | // rendered. 182 | void BeginNodeEditor(); 183 | void EndNodeEditor(); 184 | 185 | // Use PushColorStyle and PopColorStyle to modify Style::colors mid-frame. 186 | void PushColorStyle(ColorStyle item, unsigned int color); 187 | void PopColorStyle(); 188 | void PushStyleVar(StyleVar style_item, float value); 189 | void PopStyleVar(); 190 | 191 | void BeginNode(int id); 192 | void EndNode(); 193 | 194 | // Place your node title bar content (such as the node title, using ImGui::Text) 195 | // between the following function calls. These functions have to be called 196 | // before adding any attributes, or the layout of the node will be incorrect. 197 | void BeginNodeTitleBar(); 198 | void EndNodeTitleBar(); 199 | 200 | // Attributes are ImGui UI elements embedded within the node. Attributes can 201 | // have pin shapes rendered next to them. Links are created between pins. 202 | // 203 | // The activity status of an attribute can be checked via the 204 | // IsAttributeActive() and IsAnyAttributeActive() function calls. This is one 205 | // easy way of checking for any changes made to an attribute's drag float UI, 206 | // for instance. 207 | // 208 | // Each attribute id must be unique. 209 | 210 | // Create an input attribute block. The pin is rendered on left side. 211 | void BeginInputAttribute(int id, PinShape shape = PinShape_CircleFilled); 212 | void EndInputAttribute(); 213 | // Create an output attribute block. The pin is rendered on the right side. 214 | void BeginOutputAttribute(int id, PinShape shape = PinShape_CircleFilled); 215 | void EndOutputAttribute(); 216 | // Create a static attribute block. A static attribute has no pin, and therefore 217 | // can't be linked to anything. However, you can still use IsAttributeActive() 218 | // and IsAnyAttributeActive() to check for attribute activity. 219 | void BeginStaticAttribute(int id); 220 | void EndStaticAttribute(); 221 | // Can still be used with any of the three previous Begin{*}Attribute 222 | // functions. 223 | DEPRECATED void EndAttribute(); 224 | 225 | // Push a single AttributeFlags value. By default, only AttributeFlags_None is 226 | // set. 227 | void PushAttributeFlag(AttributeFlags flag); 228 | void PopAttributeFlag(); 229 | 230 | // Render a link between attributes. 231 | // The attributes ids used here must match the ids used in 232 | // Begin(Input|Output)Attribute function calls. The order of start_attr and 233 | // end_attr doesn't make a difference for rendering the link. 234 | void Link(int id, int start_attribute_id, int end_attribute_id); 235 | 236 | // Set's the node's position corresponding to the node id, either using screen 237 | // space coordinates, or node editor grid coordinates. You can even set the 238 | // position before the node has been created with BeginNode(). 239 | 240 | void SetNodeScreenSpacePos(int node_id, const ImVec2& screen_space_pos); 241 | void SetNodeGridSpacePos(int node_id, const ImVec2& grid_pos); 242 | // Enable or disable the ability to click and drag a specific node. 243 | void SetNodeDraggable(int node_id, const bool draggable); 244 | 245 | // Returns true if the current node editor canvas is being hovered over by the 246 | // mouse, and is not blocked by any other windows. 247 | bool IsEditorHovered(); 248 | // The following functions return true if a UI element is being hovered over by 249 | // the mouse cursor. Assigns the id of the UI element being hovered over to the 250 | // function argument. Use these functions after EndNodeEditor() has been called. 251 | bool IsNodeHovered(int* node_id); 252 | bool IsLinkHovered(int* link_id); 253 | bool IsPinHovered(int* attribute_id); 254 | 255 | // Use The following two functions to query the number of selected nodes or 256 | // links in the current editor. Use after calling EndNodeEditor(). 257 | int NumSelectedNodes(); 258 | int NumSelectedLinks(); 259 | // Get the selected node/link ids. The pointer argument should point to an 260 | // integer array with at least as many elements as the respective 261 | // NumSelectedNodes/NumSelectedLinks function call returned. 262 | void GetSelectedNodes(int* node_ids); 263 | void GetSelectedLinks(int* link_ids); 264 | 265 | // Was the previous attribute active? This will continuously return true while 266 | // the left mouse button is being pressed over the UI content of the attribute. 267 | bool IsAttributeActive(); 268 | // Was any attribute active? If so, sets the active attribute id to the output 269 | // function argument. 270 | bool IsAnyAttributeActive(int* attribute_id = 0); 271 | 272 | // Use the following functions to query a change of state for an existing link, 273 | // or new link. Call these after EndNodeEditor(). 274 | 275 | // Did the user start dragging a new link from a pin? 276 | bool IsLinkStarted(int* started_at_attribute_id); 277 | // Did the user drop the dragged link before attaching it to a pin? 278 | bool IsLinkDropped(); 279 | // Did the user finish creating a new link? 280 | bool IsLinkCreated(int* started_at_attribute_id, int* ended_at_attribute_id); 281 | // Was an existing link detached from a pin by the user? The detached link's id 282 | // is assigned to the output argument link_id. 283 | bool IsLinkDestroyed(int* link_id); 284 | 285 | // Use the following functions to write the editor context's state to a string, 286 | // or directly to a file. The editor context is serialized in the INI file 287 | // format. 288 | 289 | const char* SaveCurrentEditorStateToIniString(size_t* data_size = NULL); 290 | const char* SaveEditorStateToIniString( 291 | const EditorContext* editor, 292 | size_t* data_size = NULL); 293 | 294 | void LoadCurrentEditorStateFromIniString(const char* data, size_t data_size); 295 | void LoadEditorStateFromIniString( 296 | EditorContext* editor, 297 | const char* data, 298 | size_t data_size); 299 | 300 | void SaveCurrentEditorStateToIniFile(const char* file_name); 301 | void SaveEditorStateToIniFile( 302 | const EditorContext* editor, 303 | const char* file_name); 304 | 305 | void LoadCurrentEditorStateFromIniFile(const char* file_name); 306 | void LoadEditorStateFromIniFile(EditorContext* editor, const char* file_name); 307 | } // namespace imnodes 308 | -------------------------------------------------------------------------------- /qrhiimgui.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "qrhiimgui_p.h" 52 | #include 53 | #include 54 | #include 55 | 56 | QT_BEGIN_NAMESPACE 57 | 58 | QRhiImgui::QRhiImgui() 59 | : d(new QRhiImguiPrivate) 60 | { 61 | } 62 | 63 | QRhiImgui::~QRhiImgui() 64 | { 65 | releaseResources(); 66 | delete d; 67 | } 68 | 69 | void QRhiImgui::setFrameFunc(FrameFunc f) 70 | { 71 | d->frame = f; 72 | } 73 | 74 | void QRhiImgui::demoWindow() 75 | { 76 | ImGui::ShowDemoWindow(&d->showDemoWindow); 77 | } 78 | 79 | static QShader getShader(const QString &name) 80 | { 81 | QFile f(name); 82 | if (f.open(QIODevice::ReadOnly)) 83 | return QShader::fromSerialized(f.readAll()); 84 | 85 | return QShader(); 86 | } 87 | 88 | bool QRhiImgui::prepareFrame(QRhiRenderTarget *rt, QRhiRenderPassDescriptor *rp, 89 | QRhiResourceUpdateBatch *dstResourceUpdates) 90 | { 91 | ImGuiIO &io(ImGui::GetIO()); 92 | 93 | if (d->textures.isEmpty()) { 94 | unsigned char *pixels; 95 | int w, h; 96 | io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h); 97 | const QImage wrapperImg((const uchar *) pixels, w, h, QImage::Format_RGBA8888); 98 | QRhiImguiPrivate::Texture t; 99 | t.image = wrapperImg.copy(); 100 | d->textures.append(t); 101 | io.Fonts->SetTexID(reinterpret_cast(quintptr(d->textures.count() - 1))); 102 | } 103 | 104 | const QSize outputSize = rt->pixelSize(); 105 | const float dpr = rt->devicePixelRatio(); 106 | io.DisplaySize.x = outputSize.width() / dpr; 107 | io.DisplaySize.y = outputSize.height() / dpr; 108 | io.DisplayFramebufferScale = ImVec2(dpr, dpr); 109 | 110 | d->updateInput(); 111 | 112 | ImGui::NewFrame(); 113 | if (d->frame) 114 | d->frame(); 115 | ImGui::Render(); 116 | 117 | ImDrawData *draw = ImGui::GetDrawData(); 118 | draw->ScaleClipRects(ImVec2(dpr, dpr)); 119 | 120 | if (!d->ubuf) { 121 | d->ubuf = d->rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); 122 | d->ubuf->setName(QByteArrayLiteral("imgui uniform buffer")); 123 | d->releasePool << d->ubuf; 124 | if (!d->ubuf->create()) 125 | return false; 126 | 127 | float opacity = 1.0f; 128 | dstResourceUpdates->updateDynamicBuffer(d->ubuf, 64, 4, &opacity); 129 | } 130 | 131 | if (d->lastDisplaySize.width() != io.DisplaySize.x || d->lastDisplaySize.height() != io.DisplaySize.y) { 132 | QMatrix4x4 mvp = d->rhi->clipSpaceCorrMatrix(); 133 | mvp.ortho(0, io.DisplaySize.x, io.DisplaySize.y, 0, 1, -1); 134 | dstResourceUpdates->updateDynamicBuffer(d->ubuf, 0, 64, mvp.constData()); 135 | d->lastDisplaySize = QSizeF(io.DisplaySize.x, io.DisplaySize.y); // without dpr 136 | d->lastOutputSize = outputSize; // with dpr 137 | } 138 | 139 | if (!d->sampler) { 140 | d->sampler = d->rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, 141 | QRhiSampler::Repeat, QRhiSampler::Repeat); 142 | d->sampler->setName(QByteArrayLiteral("imgui sampler")); 143 | d->releasePool << d->sampler; 144 | if (!d->sampler->create()) 145 | return false; 146 | } 147 | 148 | for (int i = 0; i < d->textures.count(); ++i) { 149 | QRhiImguiPrivate::Texture &t(d->textures[i]); 150 | if (!t.tex) { 151 | t.tex = d->rhi->newTexture(QRhiTexture::RGBA8, t.image.size()); 152 | t.tex->setName(QByteArrayLiteral("imgui texture ") + QByteArray::number(i)); 153 | if (!t.tex->create()) 154 | return false; 155 | dstResourceUpdates->uploadTexture(t.tex, t.image); 156 | } 157 | if (!t.srb) { 158 | t.srb = d->rhi->newShaderResourceBindings(); 159 | t.srb->setBindings({ 160 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf), 161 | QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, t.tex, d->sampler) 162 | }); 163 | if (!t.srb->create()) 164 | return false; 165 | } 166 | } 167 | 168 | if (!d->ps) { 169 | d->ps = d->rhi->newGraphicsPipeline(); 170 | d->releasePool << d->ps; 171 | QRhiGraphicsPipeline::TargetBlend blend; 172 | blend.enable = true; 173 | blend.srcColor = QRhiGraphicsPipeline::SrcAlpha; 174 | blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha; 175 | blend.srcAlpha = QRhiGraphicsPipeline::One; 176 | blend.dstAlpha = QRhiGraphicsPipeline::Zero; 177 | blend.colorWrite = QRhiGraphicsPipeline::R | QRhiGraphicsPipeline::G | QRhiGraphicsPipeline::B; 178 | d->ps->setTargetBlends({ blend }); 179 | d->ps->setCullMode(QRhiGraphicsPipeline::None); 180 | d->ps->setDepthTest(d->depthTest); 181 | d->ps->setDepthOp(QRhiGraphicsPipeline::LessOrEqual); 182 | d->ps->setDepthWrite(false); 183 | d->ps->setFlags(QRhiGraphicsPipeline::UsesScissor); 184 | 185 | QShader vs = getShader(QLatin1String(":/imgui.vert.qsb")); 186 | Q_ASSERT(vs.isValid()); 187 | QShader fs = getShader(QLatin1String(":/imgui.frag.qsb")); 188 | Q_ASSERT(fs.isValid()); 189 | d->ps->setShaderStages({ 190 | { QRhiShaderStage::Vertex, vs }, 191 | { QRhiShaderStage::Fragment, fs } 192 | }); 193 | 194 | QRhiVertexInputLayout inputLayout; 195 | inputLayout.setBindings({ 196 | { 4 * sizeof(float) + sizeof(quint32) } 197 | }); 198 | inputLayout.setAttributes({ 199 | { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, 200 | { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }, 201 | { 0, 2, QRhiVertexInputAttribute::UNormByte4, 4 * sizeof(float) } 202 | }); 203 | 204 | d->ps->setVertexInputLayout(inputLayout); 205 | d->ps->setShaderResourceBindings(d->textures[0].srb); 206 | d->ps->setRenderPassDescriptor(rp); 207 | 208 | if (!d->ps->create()) 209 | return false; 210 | } 211 | 212 | // the imgui default 213 | Q_ASSERT(sizeof(ImDrawVert) == 20); 214 | // switched to uint in imconfig.h to avoid trouble with 4 byte offset alignment reqs 215 | Q_ASSERT(sizeof(ImDrawIdx) == 4); 216 | 217 | d->vbufOffsets.resize(draw->CmdListsCount); 218 | d->ibufOffsets.resize(draw->CmdListsCount); 219 | int totalVbufSize = 0; 220 | int totalIbufSize = 0; 221 | for (int n = 0; n < draw->CmdListsCount; ++n) { 222 | const ImDrawList *cmdList = draw->CmdLists[n]; 223 | const int vbufSize = cmdList->VtxBuffer.Size * sizeof(ImDrawVert); 224 | d->vbufOffsets[n] = totalVbufSize; 225 | totalVbufSize += vbufSize; 226 | const int ibufSize = cmdList->IdxBuffer.Size * sizeof(ImDrawIdx); 227 | d->ibufOffsets[n] = totalIbufSize; 228 | totalIbufSize += ibufSize; 229 | } 230 | 231 | if (!d->vbuf) { 232 | d->vbuf = d->rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, totalVbufSize); 233 | d->vbuf->setName(QByteArrayLiteral("imgui vertex buffer")); 234 | d->releasePool << d->vbuf; 235 | if (!d->vbuf->create()) 236 | return false; 237 | } else { 238 | if (totalVbufSize > d->vbuf->size()) { 239 | d->vbuf->setSize(totalVbufSize); 240 | if (!d->vbuf->create()) 241 | return false; 242 | } 243 | } 244 | if (!d->ibuf) { 245 | d->ibuf = d->rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::IndexBuffer, totalIbufSize); 246 | d->ibuf->setName(QByteArrayLiteral("imgui index buffer")); 247 | d->releasePool << d->ibuf; 248 | if (!d->ibuf->create()) 249 | return false; 250 | } else { 251 | if (totalIbufSize > d->ibuf->size()) { 252 | d->ibuf->setSize(totalIbufSize); 253 | if (!d->ibuf->create()) 254 | return false; 255 | } 256 | } 257 | 258 | for (int n = 0; n < draw->CmdListsCount; ++n) { 259 | const ImDrawList *cmdList = draw->CmdLists[n]; 260 | const int vbufSize = cmdList->VtxBuffer.Size * sizeof(ImDrawVert); 261 | dstResourceUpdates->updateDynamicBuffer(d->vbuf, d->vbufOffsets[n], vbufSize, cmdList->VtxBuffer.Data); 262 | const int ibufSize = cmdList->IdxBuffer.Size * sizeof(ImDrawIdx); 263 | dstResourceUpdates->updateDynamicBuffer(d->ibuf, d->ibufOffsets[n], ibufSize, cmdList->IdxBuffer.Data); 264 | } 265 | 266 | return true; 267 | } 268 | 269 | void QRhiImgui::queueFrame(QRhiCommandBuffer *cb) 270 | { 271 | QRhiCommandBuffer::VertexInput vbufBinding(d->vbuf, 0); 272 | cb->setViewport({ 0, 0, float(d->lastOutputSize.width()), float(d->lastOutputSize.height()) }); 273 | 274 | ImDrawData *draw = ImGui::GetDrawData(); 275 | for (int n = 0; n < draw->CmdListsCount; ++n) { 276 | const ImDrawList *cmdList = draw->CmdLists[n]; 277 | const ImDrawIdx *indexBufOffset = nullptr; 278 | vbufBinding.second = d->vbufOffsets[n]; 279 | 280 | for (int i = 0; i < cmdList->CmdBuffer.Size; ++i) { 281 | const ImDrawCmd *cmd = &cmdList->CmdBuffer[i]; 282 | const quint32 indexOffset = d->ibufOffsets[n] + quintptr(indexBufOffset); 283 | 284 | if (!cmd->UserCallback) { 285 | const QPointF scissorPixelBottomLeft = QPointF(cmd->ClipRect.x, d->lastOutputSize.height() - cmd->ClipRect.w); 286 | const QSizeF scissorPixelSize = QSizeF(cmd->ClipRect.z - cmd->ClipRect.x, cmd->ClipRect.w - cmd->ClipRect.y); 287 | const int textureIndex = int(reinterpret_cast(cmd->TextureId)); 288 | cb->setGraphicsPipeline(d->ps); 289 | cb->setShaderResources(d->textures[textureIndex].srb); 290 | cb->setScissor({ int(scissorPixelBottomLeft.x()), int(scissorPixelBottomLeft.y()), 291 | int(scissorPixelSize.width()), int(scissorPixelSize.height()) }); 292 | cb->setVertexInput(0, 1, &vbufBinding, d->ibuf, indexOffset, QRhiCommandBuffer::IndexUInt32); 293 | cb->drawIndexed(cmd->ElemCount); 294 | } else { 295 | cmd->UserCallback(cmdList, cmd); 296 | } 297 | 298 | indexBufOffset += cmd->ElemCount; 299 | } 300 | } 301 | } 302 | 303 | void QRhiImgui::initialize(QRhi *rhi) 304 | { 305 | d->rhi = rhi; 306 | d->lastOutputSize = QSizeF(); 307 | } 308 | 309 | void QRhiImgui::releaseResources() 310 | { 311 | for (QRhiImguiPrivate::Texture &t : d->textures) { 312 | delete t.tex; 313 | delete t.srb; 314 | } 315 | d->textures.clear(); 316 | 317 | qDeleteAll(d->releasePool); 318 | d->releasePool.clear(); 319 | 320 | d->vbuf = d->ibuf = d->ubuf = nullptr; 321 | d->ps = nullptr; 322 | d->sampler = nullptr; 323 | 324 | delete d->inputEventFilter; 325 | d->inputEventFilter = nullptr; 326 | } 327 | 328 | QRhiImgui::FrameFunc QRhiImgui::frameFunc() const 329 | { 330 | return d->frame; 331 | } 332 | 333 | void QRhiImgui::setInputEventSource(QObject *src) 334 | { 335 | if (d->inputEventSource && d->inputEventFilter) 336 | d->inputEventSource->removeEventFilter(d->inputEventFilter); 337 | 338 | d->inputEventSource = src; 339 | 340 | if (!d->inputEventFilter) { 341 | d->inputEventFilter = new QRhiImGuiInputEventFilter; 342 | d->inputInitialized = false; 343 | } 344 | 345 | d->inputEventSource->installEventFilter(d->inputEventFilter); 346 | } 347 | 348 | void QRhiImgui::setEatInputEvents(bool enabled) 349 | { 350 | if (d->inputEventFilter) 351 | d->inputEventFilter->eatEvents = enabled; 352 | } 353 | 354 | void QRhiImgui::setDepthTest(bool enabled) 355 | { 356 | d->depthTest = enabled; 357 | } 358 | 359 | QRhiImguiPrivate::QRhiImguiPrivate() 360 | { 361 | ImGui::CreateContext(); 362 | } 363 | 364 | QRhiImguiPrivate::~QRhiImguiPrivate() 365 | { 366 | ImGui::DestroyContext(); 367 | } 368 | 369 | bool QRhiImGuiInputEventFilter::eventFilter(QObject *, QEvent *event) 370 | { 371 | switch (event->type()) { 372 | case QEvent::MouseButtonPress: 373 | case QEvent::MouseMove: 374 | case QEvent::MouseButtonRelease: 375 | { 376 | QMouseEvent *me = static_cast(event); 377 | mousePos = me->pos(); 378 | mouseButtonsDown = me->buttons(); 379 | modifiers = me->modifiers(); 380 | } 381 | return eatEvents; 382 | 383 | case QEvent::Wheel: 384 | { 385 | QWheelEvent *we = static_cast(event); 386 | mouseWheel += we->angleDelta().y() / 120.0f; 387 | } 388 | return eatEvents; 389 | 390 | case QEvent::KeyPress: 391 | case QEvent::KeyRelease: 392 | { 393 | const bool down = event->type() == QEvent::KeyPress; 394 | QKeyEvent *ke = static_cast(event); 395 | modifiers = ke->modifiers(); 396 | if (down) 397 | keyText.append(ke->text()); 398 | int k = ke->key(); 399 | if (k <= 0xFF) 400 | keyDown[k] = down; 401 | else if (k >= FIRSTSPECKEY && k <= LASTSPECKEY) 402 | keyDown[MAPSPECKEY(k)] = down; 403 | } 404 | return eatEvents; 405 | 406 | default: 407 | break; 408 | } 409 | 410 | return false; 411 | } 412 | 413 | void QRhiImguiPrivate::updateInput() 414 | { 415 | if (!inputEventFilter) 416 | return; 417 | 418 | ImGuiIO &io = ImGui::GetIO(); 419 | 420 | if (!inputInitialized) { 421 | inputInitialized = true; 422 | 423 | io.KeyMap[ImGuiKey_Tab] = MAPSPECKEY(Qt::Key_Tab); 424 | io.KeyMap[ImGuiKey_LeftArrow] = MAPSPECKEY(Qt::Key_Left); 425 | io.KeyMap[ImGuiKey_RightArrow] = MAPSPECKEY(Qt::Key_Right); 426 | io.KeyMap[ImGuiKey_UpArrow] = MAPSPECKEY(Qt::Key_Up); 427 | io.KeyMap[ImGuiKey_DownArrow] = MAPSPECKEY(Qt::Key_Down); 428 | io.KeyMap[ImGuiKey_PageUp] = MAPSPECKEY(Qt::Key_PageUp); 429 | io.KeyMap[ImGuiKey_PageDown] = MAPSPECKEY(Qt::Key_PageDown); 430 | io.KeyMap[ImGuiKey_Home] = MAPSPECKEY(Qt::Key_Home); 431 | io.KeyMap[ImGuiKey_End] = MAPSPECKEY(Qt::Key_End); 432 | io.KeyMap[ImGuiKey_Delete] = MAPSPECKEY(Qt::Key_Delete); 433 | io.KeyMap[ImGuiKey_Backspace] = MAPSPECKEY(Qt::Key_Backspace); 434 | io.KeyMap[ImGuiKey_Enter] = MAPSPECKEY(Qt::Key_Return); 435 | io.KeyMap[ImGuiKey_Escape] = MAPSPECKEY(Qt::Key_Escape); 436 | 437 | io.KeyMap[ImGuiKey_A] = Qt::Key_A; 438 | io.KeyMap[ImGuiKey_C] = Qt::Key_C; 439 | io.KeyMap[ImGuiKey_V] = Qt::Key_V; 440 | io.KeyMap[ImGuiKey_X] = Qt::Key_X; 441 | io.KeyMap[ImGuiKey_Y] = Qt::Key_Y; 442 | io.KeyMap[ImGuiKey_Z] = Qt::Key_Z; 443 | } 444 | 445 | QRhiImGuiInputEventFilter *w = inputEventFilter; 446 | 447 | io.MousePos = ImVec2(w->mousePos.x(), w->mousePos.y()); 448 | 449 | io.MouseDown[0] = w->mouseButtonsDown.testFlag(Qt::LeftButton); 450 | io.MouseDown[1] = w->mouseButtonsDown.testFlag(Qt::RightButton); 451 | io.MouseDown[2] = w->mouseButtonsDown.testFlag(Qt::MiddleButton); 452 | 453 | io.MouseWheel = w->mouseWheel; 454 | w->mouseWheel = 0; 455 | 456 | io.KeyCtrl = w->modifiers.testFlag(Qt::ControlModifier); 457 | io.KeyShift = w->modifiers.testFlag(Qt::ShiftModifier); 458 | io.KeyAlt = w->modifiers.testFlag(Qt::AltModifier); 459 | io.KeySuper = w->modifiers.testFlag(Qt::MetaModifier); 460 | 461 | memcpy(io.KeysDown, w->keyDown, sizeof(w->keyDown)); 462 | 463 | if (!w->keyText.isEmpty()) { 464 | for (const QChar &c : w->keyText) { 465 | ImWchar u = c.unicode(); 466 | if (u) 467 | io.AddInputCharacter(u); 468 | } 469 | w->keyText.clear(); 470 | } 471 | } 472 | 473 | QT_END_NAMESPACE 474 | -------------------------------------------------------------------------------- /qrhiimgui.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef QRHIIMGUI_H 52 | #define QRHIIMGUI_H 53 | 54 | #include 55 | #include 56 | 57 | QT_BEGIN_NAMESPACE 58 | 59 | class QRhiImguiPrivate; 60 | 61 | class QRhiImgui 62 | { 63 | public: 64 | QRhiImgui(); 65 | ~QRhiImgui(); 66 | 67 | typedef std::function FrameFunc; 68 | void setFrameFunc(FrameFunc f); 69 | FrameFunc frameFunc() const; 70 | void demoWindow(); 71 | 72 | void setInputEventSource(QObject *src); 73 | void setEatInputEvents(bool enabled); 74 | void setDepthTest(bool enabled); 75 | 76 | void initialize(QRhi *rhi); 77 | void releaseResources(); 78 | 79 | // We could have chosen to provide a single function that queues an entire 80 | // pass, but that would be inflexible and (in some cases) less performant. 81 | // Instead, make it possible to combine with some other pass. 82 | bool prepareFrame(QRhiRenderTarget *rt, QRhiRenderPassDescriptor *rp, 83 | QRhiResourceUpdateBatch *dstResourceUpdates); 84 | void queueFrame(QRhiCommandBuffer *cb); 85 | 86 | private: 87 | Q_DISABLE_COPY(QRhiImgui) 88 | QRhiImguiPrivate *d = nullptr; 89 | }; 90 | 91 | QT_END_NAMESPACE 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /qrhiimgui.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS += qwindow quick 3 | -------------------------------------------------------------------------------- /qrhiimgui_p.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef QRHIIMGUI_P_H 52 | #define QRHIIMGUI_P_H 53 | 54 | // 55 | // W A R N I N G 56 | // ------------- 57 | // 58 | // This file is not part of the Qt API. It exists purely as an 59 | // implementation detail. This header file may change from version to 60 | // version without notice, or even be removed. 61 | // 62 | // We mean it. 63 | // 64 | 65 | #include "qrhiimgui.h" 66 | 67 | #include "imgui.h" 68 | 69 | QT_BEGIN_NAMESPACE 70 | 71 | #define FIRSTSPECKEY (0x01000000) 72 | #define LASTSPECKEY (0x01000017) 73 | #define MAPSPECKEY(k) ((k) - FIRSTSPECKEY + 256) 74 | 75 | class QRhiImGuiInputEventFilter : public QObject 76 | { 77 | public: 78 | QRhiImGuiInputEventFilter() 79 | { 80 | memset(keyDown, 0, sizeof(keyDown)); 81 | } 82 | 83 | bool eventFilter(QObject *watched, QEvent *event) override; 84 | 85 | bool eatEvents = false; 86 | QPointF mousePos; 87 | Qt::MouseButtons mouseButtonsDown = Qt::NoButton; 88 | float mouseWheel = 0; 89 | Qt::KeyboardModifiers modifiers = Qt::NoModifier; 90 | bool keyDown[256 + (LASTSPECKEY - FIRSTSPECKEY + 1)]; 91 | QString keyText; 92 | }; 93 | 94 | class QRhiImguiPrivate 95 | { 96 | public: 97 | QRhiImguiPrivate(); 98 | ~QRhiImguiPrivate(); 99 | 100 | void updateInput(); 101 | 102 | QRhiImgui::FrameFunc frame = nullptr; 103 | bool showDemoWindow = true; 104 | 105 | QRhi *rhi = nullptr; 106 | 107 | struct Texture { 108 | QImage image; 109 | QRhiTexture *tex = nullptr; 110 | QRhiShaderResourceBindings *srb = nullptr; 111 | }; 112 | QVector textures; 113 | 114 | QRhiBuffer *vbuf = nullptr; 115 | QRhiBuffer *ibuf = nullptr; 116 | QRhiBuffer *ubuf = nullptr; 117 | QRhiGraphicsPipeline *ps = nullptr; 118 | QRhiSampler *sampler = nullptr; 119 | QVector releasePool; 120 | QSizeF lastDisplaySize; 121 | QSizeF lastOutputSize; 122 | QVarLengthArray vbufOffsets; 123 | QVarLengthArray ibufOffsets; 124 | 125 | QRhiImGuiInputEventFilter *inputEventFilter = nullptr; 126 | QObject *inputEventSource = nullptr; 127 | bool inputInitialized = false; 128 | 129 | bool depthTest = true; 130 | }; 131 | 132 | QT_END_NAMESPACE 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /quick/globalstate.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef GLOBALSTATE_H 52 | #define GLOBALSTATE_H 53 | 54 | #include 55 | 56 | class GlobalState : public QObject 57 | { 58 | Q_OBJECT 59 | Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) 60 | 61 | public: 62 | bool isVisible() const { return v; } 63 | void setVisible(bool b) { if (v != b) { v = b; emit visibleChanged(); } } 64 | 65 | struct Link { 66 | int id; 67 | int fromAttrId; 68 | int toAttrId; 69 | }; 70 | QVector links; 71 | 72 | signals: 73 | void visibleChanged(); 74 | 75 | private: 76 | bool v = true; 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /quick/gui.cpp: -------------------------------------------------------------------------------- 1 | #include "gui.h" 2 | #include "globalstate.h" 3 | #include "imnodes.h" 4 | #include "TextEditor.h" 5 | 6 | extern GlobalState globalState; 7 | 8 | struct Editor : public TextEditor 9 | { 10 | Editor() { 11 | SetText(R"(#version 440 12 | 13 | layout(location = 0) in vec2 v_texcoord; 14 | layout(location = 1) in vec4 v_color; 15 | 16 | layout(location = 0) out vec4 fragColor; 17 | 18 | layout(std140, binding = 0) uniform buf { 19 | mat4 mvp; 20 | float opacity; 21 | } ubuf; 22 | 23 | layout(binding = 1) uniform sampler2D tex; 24 | 25 | void main() 26 | { 27 | vec4 c = v_color * texture(tex, v_texcoord); 28 | fragColor = vec4(c.rgb, c.a * ubuf.opacity); 29 | })"); 30 | SetLanguageDefinition(LanguageDefinition::GLSL()); 31 | SetPalette(GetDarkPalette()); 32 | 33 | ErrorMarkers markers; 34 | markers.insert({ 8, "Blah blah blah\nblah" }); 35 | SetErrorMarkers(markers); 36 | 37 | // TextEditor::Breakpoints b; 38 | // b.insert(6); 39 | // SetBreakpoints(b); 40 | } 41 | }; 42 | 43 | static Editor e; 44 | 45 | void Gui::init() 46 | { 47 | imnodes::Initialize(); 48 | imnodes::SetNodeGridSpacePos(1, ImVec2(10.0f, 10.0f)); 49 | imnodes::SetNodeGridSpacePos(3, ImVec2(10.0f, 120.0f)); 50 | imnodes::SetNodeGridSpacePos(50, ImVec2(10.0f, 240.0f)); 51 | imnodes::SetNodeGridSpacePos(5, ImVec2(200.0f, 10.0f)); 52 | imnodes::SetNodeGridSpacePos(8, ImVec2(200.0f, 100.0f)); 53 | imnodes::SetNodeGridSpacePos(13, ImVec2(300.0f, 300.0f)); 54 | } 55 | 56 | void Gui::cleanup() 57 | { 58 | imnodes::Shutdown(); 59 | } 60 | 61 | void Gui::frame() 62 | { 63 | { 64 | ImGui::Begin("Control"); 65 | if (ImGui::Button("Close ImGui overlay")) 66 | globalState.setVisible(false); 67 | ImGui::End(); 68 | } 69 | 70 | { 71 | ImGui::SetNextWindowPos(ImVec2(50, 300), ImGuiCond_FirstUseEver); 72 | ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); 73 | ImGui::Begin("imnodes"); 74 | 75 | imnodes::PushAttributeFlag(imnodes::AttributeFlags_EnableLinkDetachWithDragClick); 76 | imnodes::BeginNodeEditor(); 77 | 78 | imnodes::BeginNode(1); 79 | imnodes::BeginNodeTitleBar(); 80 | ImGui::TextUnformatted("In node"); 81 | imnodes::EndNodeTitleBar(); 82 | static float x = 0, y = 0; 83 | imnodes::BeginStaticAttribute(100); 84 | ImGui::PushItemWidth(50); 85 | ImGui::InputFloat("X", &x); 86 | ImGui::PopItemWidth(); 87 | imnodes::EndStaticAttribute(); 88 | imnodes::BeginStaticAttribute(101); 89 | ImGui::PushItemWidth(50); 90 | ImGui::InputFloat("Y", &y); 91 | ImGui::PopItemWidth(); 92 | imnodes::EndStaticAttribute(); 93 | imnodes::BeginOutputAttribute(2); 94 | ImGui::Indent(40); 95 | ImGui::Text("output"); 96 | imnodes::EndOutputAttribute(); 97 | imnodes::EndNode(); 98 | 99 | imnodes::BeginNode(3); 100 | imnodes::BeginNodeTitleBar(); 101 | ImGui::TextUnformatted("In node 2"); 102 | imnodes::EndNodeTitleBar(); 103 | static float r = 0, g = 0, b = 0; 104 | imnodes::BeginStaticAttribute(200); 105 | ImGui::PushItemWidth(50); 106 | ImGui::SliderFloat("R", &r, 0.0f, 1.0f); 107 | ImGui::PopItemWidth(); 108 | imnodes::EndStaticAttribute(); 109 | imnodes::BeginStaticAttribute(201); 110 | ImGui::PushItemWidth(50); 111 | ImGui::SliderFloat("G", &g, 0.0f, 1.0f); 112 | ImGui::PopItemWidth(); 113 | imnodes::EndStaticAttribute(); 114 | imnodes::BeginStaticAttribute(202); 115 | ImGui::PushItemWidth(50); 116 | ImGui::SliderFloat("B", &b, 0.0f, 1.0f); 117 | ImGui::PopItemWidth(); 118 | imnodes::EndStaticAttribute(); 119 | ImGui::SameLine(); 120 | imnodes::BeginOutputAttribute(4); 121 | ImGui::Indent(40); 122 | ImGui::Text("output"); 123 | imnodes::EndOutputAttribute(); 124 | imnodes::EndNode(); 125 | 126 | imnodes::BeginNode(50); 127 | imnodes::BeginNodeTitleBar(); 128 | ImGui::TextUnformatted("In node 3"); 129 | imnodes::EndNodeTitleBar(); 130 | imnodes::BeginStaticAttribute(300); 131 | ImGui::PushItemWidth(50); 132 | static int uvItem = 0; 133 | const char *uvItems[] = { "UV0", "UV1", "UV2" }; 134 | ImGui::Combo("", &uvItem, uvItems, 3); 135 | ImGui::PopItemWidth(); 136 | imnodes::EndStaticAttribute(); 137 | ImGui::SameLine(); 138 | imnodes::BeginOutputAttribute(51); 139 | ImGui::Indent(40); 140 | ImGui::Text("output"); 141 | imnodes::EndOutputAttribute(); 142 | imnodes::EndNode(); 143 | 144 | imnodes::BeginNode(5); 145 | imnodes::BeginNodeTitleBar(); 146 | ImGui::TextUnformatted("Some node"); 147 | imnodes::EndNodeTitleBar(); 148 | imnodes::BeginInputAttribute(6); 149 | ImGui::Text("input"); 150 | imnodes::EndInputAttribute(); 151 | imnodes::BeginOutputAttribute(7); 152 | ImGui::Indent(40); 153 | ImGui::Text("output"); 154 | imnodes::EndOutputAttribute(); 155 | imnodes::EndNode(); 156 | 157 | imnodes::BeginNode(8); 158 | imnodes::BeginNodeTitleBar(); 159 | ImGui::TextUnformatted("Some other node"); 160 | imnodes::EndNodeTitleBar(); 161 | imnodes::BeginInputAttribute(9); 162 | ImGui::Text("input"); 163 | imnodes::EndInputAttribute(); 164 | imnodes::BeginInputAttribute(10); 165 | ImGui::Text("input2"); 166 | imnodes::EndInputAttribute(); 167 | imnodes::BeginInputAttribute(11); 168 | ImGui::Text("input3"); 169 | imnodes::EndInputAttribute(); 170 | imnodes::BeginOutputAttribute(12); 171 | ImGui::Indent(40); 172 | ImGui::Text("output"); 173 | imnodes::EndOutputAttribute(); 174 | imnodes::EndNode(); 175 | 176 | imnodes::BeginNode(13); 177 | imnodes::BeginNodeTitleBar(); 178 | ImGui::TextUnformatted("Out node"); 179 | imnodes::EndNodeTitleBar(); 180 | imnodes::BeginInputAttribute(14); 181 | ImGui::Text("input"); 182 | imnodes::EndInputAttribute(); 183 | imnodes::EndNode(); 184 | 185 | for (const auto &link : globalState.links) 186 | imnodes::Link(link.id, link.fromAttrId, link.toAttrId); 187 | 188 | imnodes::EndNodeEditor(); 189 | imnodes::PopAttributeFlag(); 190 | 191 | static int nextLinkId = 7; 192 | int id, idFrom, idTo; 193 | if (imnodes::IsLinkCreated(&idFrom, &idTo)) 194 | globalState.links.append({ nextLinkId++, idFrom, idTo }); 195 | 196 | if (imnodes::IsLinkDestroyed(&id)) { 197 | globalState.links.erase(std::remove_if(globalState.links.begin(), globalState.links.end(), 198 | [id](const GlobalState::Link &link) { return link.id == id; }), 199 | globalState.links.end()); 200 | } 201 | 202 | ImGui::End(); 203 | } 204 | 205 | { 206 | ImGui::SetNextWindowPos(ImVec2(300, 10), ImGuiCond_FirstUseEver); 207 | ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); 208 | ImGui::Begin("ImGuiColorTextEdit"); 209 | e.Render("Hello world"); 210 | ImGui::End(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /quick/gui.h: -------------------------------------------------------------------------------- 1 | #ifndef GUI_H 2 | #define GUI_H 3 | 4 | #include "imgui.h" 5 | 6 | struct Gui 7 | { 8 | void init(); 9 | void cleanup(); 10 | void frame(); 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /quick/imguidemo.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | ../imgui.vert.qsb 5 | ../imgui.frag.qsb 6 | 7 | 8 | -------------------------------------------------------------------------------- /quick/main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include 52 | #include 53 | #include "globalstate.h" 54 | #include "qrhiimgui.h" 55 | #include "gui.h" 56 | #include "imnodes.h" 57 | 58 | struct ImGuiQuick 59 | { 60 | ImGuiQuick(); 61 | ~ImGuiQuick(); 62 | 63 | void setWindow(QQuickWindow *window); 64 | void setVisible(bool b); 65 | 66 | void init(); 67 | void release(); 68 | void prepare(); 69 | void render(); 70 | 71 | QRhiImgui d; 72 | QQuickWindow *w = nullptr; 73 | QRhi *rhi = nullptr; 74 | QRhiSwapChain *swapchain = nullptr; 75 | bool visible = true; 76 | }; 77 | 78 | ImGuiQuick::ImGuiQuick() 79 | { 80 | d.setDepthTest(false); 81 | } 82 | 83 | ImGuiQuick::~ImGuiQuick() 84 | { 85 | release(); 86 | } 87 | 88 | void ImGuiQuick::setWindow(QQuickWindow *window) 89 | { 90 | d.setInputEventSource(window); 91 | d.setEatInputEvents(true); 92 | w = window; 93 | } 94 | 95 | void ImGuiQuick::setVisible(bool b) 96 | { 97 | if (visible == b) 98 | return; 99 | 100 | visible = b; 101 | d.setEatInputEvents(visible); 102 | } 103 | 104 | void ImGuiQuick::init() 105 | { // render thread 106 | QSGRendererInterface *rif = w->rendererInterface(); 107 | rhi = static_cast(rif->getResource(w, QSGRendererInterface::RhiResource)); 108 | if (!rhi) 109 | qFatal("Failed to retrieve QRhi from QQuickWindow"); 110 | swapchain = static_cast(rif->getResource(w, QSGRendererInterface::RhiSwapchainResource)); 111 | if (!swapchain) 112 | qFatal("Failed to retrieve QRhiSwapChain from QQuickWindow"); 113 | 114 | d.initialize(rhi); 115 | } 116 | 117 | void ImGuiQuick::release() 118 | { // render thread 119 | d.releaseResources(); 120 | w = nullptr; 121 | } 122 | 123 | void ImGuiQuick::prepare() 124 | { // render thread 125 | if (!w) 126 | return; 127 | 128 | QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); 129 | d.prepareFrame(swapchain->currentFrameRenderTarget(), swapchain->renderPassDescriptor(), u); 130 | swapchain->currentFrameCommandBuffer()->resourceUpdate(u); 131 | } 132 | 133 | void ImGuiQuick::render() 134 | { // render thread 135 | if (!w || !visible) 136 | return; 137 | 138 | d.queueFrame(swapchain->currentFrameCommandBuffer()); 139 | } 140 | 141 | GlobalState globalState; 142 | 143 | int main(int argc, char *argv[]) 144 | { 145 | qputenv("QSG_INFO", "1"); 146 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 147 | QGuiApplication app(argc, argv); 148 | 149 | qmlRegisterSingletonInstance("imguidemo", 1, 0, "GlobalState", &globalState); 150 | 151 | Gui gui; 152 | 153 | // Either ig should outlive view, or should not rely on sceneGraphInvalidated. Chose the former here. 154 | ImGuiQuick ig; 155 | QQuickView view; 156 | 157 | QObject::connect(&view, &QQuickWindow::sceneGraphInitialized, &view, [&ig] { ig.init(); }, Qt::DirectConnection); 158 | QObject::connect(&view, &QQuickWindow::sceneGraphInvalidated, &view, [&ig] { ig.release(); }, Qt::DirectConnection); 159 | QObject::connect(&view, &QQuickWindow::beforeRendering, &view, [&ig] { ig.prepare(); }, Qt::DirectConnection); 160 | QObject::connect(&view, &QQuickWindow::afterRenderPassRecording, &view, [&ig] { ig.render(); }, Qt::DirectConnection); 161 | 162 | ig.setWindow(&view); 163 | ig.d.setFrameFunc( 164 | [&ig, &gui] { 165 | gui.frame(); 166 | ig.d.demoWindow(); 167 | }); 168 | 169 | ImGui::GetIO().IniFilename = nullptr; // don't save and restore layout 170 | 171 | // start out as hidden 172 | ig.setVisible(false); 173 | globalState.setVisible(false); 174 | QObject::connect(&globalState, &GlobalState::visibleChanged, &view, [&ig] { 175 | ig.setVisible(globalState.isVisible()); 176 | }); 177 | 178 | gui.init(); 179 | 180 | view.setColor(Qt::black); 181 | view.setResizeMode(QQuickView::SizeRootObjectToView); 182 | view.resize(1280, 720); 183 | view.setSource(QUrl("qrc:/main.qml")); 184 | view.show(); 185 | 186 | int r = app.exec(); 187 | 188 | gui.cleanup(); 189 | return r; 190 | } 191 | -------------------------------------------------------------------------------- /quick/main.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2020 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | import QtQuick 2.15 52 | import imguidemo 1.0 53 | 54 | Item { 55 | Text { 56 | color: "#ffffff" 57 | style: Text.Outline 58 | styleColor: "#606060" 59 | font.pixelSize: 28 60 | property int api: GraphicsInfo.api 61 | text: { 62 | if (GraphicsInfo.api === GraphicsInfo.OpenGLRhi) 63 | "OpenGL on QRhi"; 64 | else if (GraphicsInfo.api === GraphicsInfo.Direct3D11Rhi) 65 | "D3D11 on QRhi"; 66 | else if (GraphicsInfo.api === GraphicsInfo.VulkanRhi) 67 | "Vulkan on QRhi"; 68 | else if (GraphicsInfo.api === GraphicsInfo.MetalRhi) 69 | "Metal on QRhi"; 70 | else if (GraphicsInfo.api === GraphicsInfo.Null) 71 | "Null on QRhi"; 72 | else 73 | "Unknown API"; 74 | } 75 | } 76 | 77 | Rectangle { 78 | color: "red" 79 | width: 300 80 | height: 300 81 | anchors.centerIn: parent 82 | NumberAnimation on rotation { from: 0; to: 360; duration: 5000; loops: -1 } 83 | } 84 | 85 | Text { 86 | anchors.centerIn: parent 87 | color: "#ffffff" 88 | style: Text.Outline 89 | styleColor: "#606060" 90 | font.pixelSize: 28 91 | property int api: GraphicsInfo.api 92 | text: "Click to open ImGui overlay" 93 | visible: !GlobalState.visible 94 | } 95 | 96 | MouseArea { 97 | anchors.fill: parent 98 | onClicked: GlobalState.visible = !GlobalState.visible 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /quick/quick.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += quick gui-private 4 | 5 | SOURCES = \ 6 | main.cpp \ 7 | gui.cpp \ 8 | ../qrhiimgui.cpp \ 9 | ../imgui/imgui.cpp \ 10 | ../imgui/imgui_draw.cpp \ 11 | ../imgui/imgui_widgets.cpp \ 12 | ../imgui/imgui_demo.cpp \ 13 | ../imnodes/imnodes.cpp \ 14 | ../ImGuiColorTextEdit/TextEditor.cpp 15 | 16 | HEADERS = \ 17 | globalstate.h \ 18 | gui.h \ 19 | ../qrhiimgui.h \ 20 | ../qrhiimgui_p.h 21 | 22 | INCLUDEPATH += .. ../imgui ../imnodes ../ImGuiColorTextEdit 23 | 24 | RESOURCES = \ 25 | imguidemo.qrc 26 | -------------------------------------------------------------------------------- /qwindow/examplefw.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2018 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | // Adapted from hellominimalcrossgfxtriangle with the frame rendering stripped out. 52 | // Include this file and implement Window::customInit, release and render. 53 | // Debug/validation layer is enabled for D3D and Vulkan. 54 | 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | #ifndef QT_NO_OPENGL 68 | #include 69 | #include 70 | #endif 71 | 72 | #if QT_CONFIG(vulkan) 73 | #include 74 | #include 75 | #endif 76 | 77 | #ifdef Q_OS_WIN 78 | #include 79 | #endif 80 | 81 | #ifdef Q_OS_DARWIN 82 | #include 83 | #endif 84 | 85 | QShader getShader(const QString &name) 86 | { 87 | QFile f(name); 88 | if (f.open(QIODevice::ReadOnly)) 89 | return QShader::fromSerialized(f.readAll()); 90 | 91 | return QShader(); 92 | } 93 | 94 | enum GraphicsApi 95 | { 96 | Null, 97 | OpenGL, 98 | Vulkan, 99 | D3D11, 100 | Metal 101 | }; 102 | 103 | GraphicsApi graphicsApi; 104 | 105 | QString graphicsApiName() 106 | { 107 | switch (graphicsApi) { 108 | case Null: 109 | return QLatin1String("Null (no output)"); 110 | case OpenGL: 111 | return QLatin1String("OpenGL 2.x"); 112 | case Vulkan: 113 | return QLatin1String("Vulkan"); 114 | case D3D11: 115 | return QLatin1String("Direct3D 11"); 116 | case Metal: 117 | return QLatin1String("Metal"); 118 | default: 119 | break; 120 | } 121 | return QString(); 122 | } 123 | 124 | QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers; 125 | int sampleCount = 1; 126 | QRhiSwapChain::Flags scFlags = 0; 127 | QRhi::EndFrameFlags endFrameFlags = 0; 128 | 129 | class Window : public QWindow 130 | { 131 | public: 132 | Window(); 133 | ~Window(); 134 | 135 | protected: 136 | void init(); 137 | void releaseResources(); 138 | void resizeSwapChain(); 139 | void releaseSwapChain(); 140 | void render(); 141 | 142 | void customInit(); 143 | void customRelease(); 144 | void customRender(); 145 | 146 | void exposeEvent(QExposeEvent *) override; 147 | bool event(QEvent *) override; 148 | 149 | bool m_running = false; 150 | bool m_notExposed = false; 151 | bool m_newlyExposed = false; 152 | 153 | QRhi *m_r = nullptr; 154 | bool m_hasSwapChain = false; 155 | QRhiSwapChain *m_sc = nullptr; 156 | QRhiRenderBuffer *m_ds = nullptr; 157 | QRhiRenderPassDescriptor *m_rp = nullptr; 158 | 159 | QMatrix4x4 m_proj; 160 | 161 | QElapsedTimer m_timer; 162 | int m_frameCount; 163 | 164 | #ifndef QT_NO_OPENGL 165 | QOffscreenSurface *m_fallbackSurface = nullptr; 166 | #endif 167 | 168 | friend int main(int, char**); 169 | }; 170 | 171 | Window::Window() 172 | { 173 | // Tell the platform plugin what we want. 174 | switch (graphicsApi) { 175 | case OpenGL: 176 | setSurfaceType(OpenGLSurface); 177 | setFormat(QRhiGles2InitParams::adjustedFormat()); 178 | break; 179 | case Vulkan: 180 | setSurfaceType(VulkanSurface); 181 | break; 182 | case D3D11: 183 | setSurfaceType(OpenGLSurface); // not a typo 184 | break; 185 | case Metal: 186 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) 187 | setSurfaceType(MetalSurface); 188 | #endif 189 | break; 190 | default: 191 | break; 192 | } 193 | } 194 | 195 | Window::~Window() 196 | { 197 | releaseResources(); 198 | } 199 | 200 | void Window::exposeEvent(QExposeEvent *) 201 | { 202 | // initialize and start rendering when the window becomes usable for graphics purposes 203 | if (isExposed() && !m_running) { 204 | m_running = true; 205 | init(); 206 | resizeSwapChain(); 207 | } 208 | 209 | const QSize surfaceSize = m_hasSwapChain ? m_sc->surfacePixelSize() : QSize(); 210 | 211 | // stop pushing frames when not exposed (or size is 0) 212 | if ((!isExposed() || (m_hasSwapChain && surfaceSize.isEmpty())) && m_running) 213 | m_notExposed = true; 214 | 215 | // continue when exposed again and the surface has a valid size. 216 | // note that the surface size can be (0, 0) even though size() reports a valid one... 217 | if (isExposed() && m_running && m_notExposed && !surfaceSize.isEmpty()) { 218 | m_notExposed = false; 219 | m_newlyExposed = true; 220 | } 221 | 222 | // always render a frame on exposeEvent() (when exposed) in order to update 223 | // immediately on window resize. 224 | if (isExposed() && !surfaceSize.isEmpty()) 225 | render(); 226 | } 227 | 228 | bool Window::event(QEvent *e) 229 | { 230 | switch (e->type()) { 231 | case QEvent::UpdateRequest: 232 | render(); 233 | break; 234 | 235 | case QEvent::PlatformSurface: 236 | // this is the proper time to tear down the swapchain (while the native window and surface are still around) 237 | if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) 238 | releaseSwapChain(); 239 | break; 240 | 241 | default: 242 | break; 243 | } 244 | 245 | return QWindow::event(e); 246 | } 247 | 248 | void Window::init() 249 | { 250 | if (graphicsApi == Null) { 251 | QRhiNullInitParams params; 252 | m_r = QRhi::create(QRhi::Null, ¶ms, rhiFlags); 253 | } 254 | 255 | #ifndef QT_NO_OPENGL 256 | if (graphicsApi == OpenGL) { 257 | m_fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); 258 | QRhiGles2InitParams params; 259 | params.fallbackSurface = m_fallbackSurface; 260 | params.window = this; 261 | m_r = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags); 262 | } 263 | #endif 264 | 265 | #if QT_CONFIG(vulkan) 266 | if (graphicsApi == Vulkan) { 267 | QRhiVulkanInitParams params; 268 | params.inst = vulkanInstance(); 269 | params.window = this; 270 | m_r = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); 271 | } 272 | #endif 273 | 274 | #ifdef Q_OS_WIN 275 | if (graphicsApi == D3D11) { 276 | QRhiD3D11InitParams params; 277 | params.enableDebugLayer = true; 278 | m_r = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); 279 | } 280 | #endif 281 | 282 | #ifdef Q_OS_DARWIN 283 | if (graphicsApi == Metal) { 284 | QRhiMetalInitParams params; 285 | m_r = QRhi::create(QRhi::Metal, ¶ms, rhiFlags); 286 | } 287 | #endif 288 | 289 | if (!m_r) 290 | qFatal("Failed to create RHI backend"); 291 | 292 | // now onto the backend-independent init 293 | 294 | m_sc = m_r->newSwapChain(); 295 | // allow depth-stencil, although we do not actually enable depth test/write for the triangle 296 | m_ds = m_r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, 297 | QSize(), // no need to set the size yet 298 | sampleCount, 299 | QRhiRenderBuffer::UsedWithSwapChainOnly); 300 | m_sc->setWindow(this); 301 | m_sc->setDepthStencil(m_ds); 302 | m_sc->setSampleCount(sampleCount); 303 | m_sc->setFlags(scFlags); 304 | m_rp = m_sc->newCompatibleRenderPassDescriptor(); 305 | m_sc->setRenderPassDescriptor(m_rp); 306 | 307 | customInit(); 308 | } 309 | 310 | void Window::releaseResources() 311 | { 312 | customRelease(); 313 | 314 | delete m_rp; 315 | m_rp = nullptr; 316 | 317 | delete m_ds; 318 | m_ds = nullptr; 319 | 320 | delete m_sc; 321 | m_sc = nullptr; 322 | 323 | delete m_r; 324 | m_r = nullptr; 325 | 326 | #ifndef QT_NO_OPENGL 327 | delete m_fallbackSurface; 328 | m_fallbackSurface = nullptr; 329 | #endif 330 | } 331 | 332 | void Window::resizeSwapChain() 333 | { 334 | const QSize outputSize = m_sc->surfacePixelSize(); 335 | 336 | m_ds->setPixelSize(outputSize); 337 | m_ds->create(); // == m_ds->destroy(); m_ds->create(); 338 | 339 | m_hasSwapChain = m_sc->createOrResize(); 340 | 341 | m_frameCount = 0; 342 | m_timer.restart(); 343 | 344 | m_proj = m_r->clipSpaceCorrMatrix(); 345 | m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); 346 | m_proj.translate(0, 0, -4); 347 | } 348 | 349 | void Window::releaseSwapChain() 350 | { 351 | if (m_hasSwapChain) { 352 | m_hasSwapChain = false; 353 | m_sc->destroy(); 354 | } 355 | } 356 | 357 | void Window::render() 358 | { 359 | if (!m_hasSwapChain || m_notExposed) 360 | return; 361 | 362 | // If the window got resized or got newly exposed, resize the swapchain. 363 | // (the newly-exposed case is not actually required by some 364 | // platforms/backends, but f.ex. Vulkan on Windows seems to need it) 365 | if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { 366 | resizeSwapChain(); 367 | if (!m_hasSwapChain) 368 | return; 369 | m_newlyExposed = false; 370 | } 371 | 372 | // Start a new frame. This is where we block when too far ahead of 373 | // GPU/present, and that's what throttles the thread to the refresh rate. 374 | // (except for OpenGL where it happens either in endFrame or somewhere else 375 | // depending on the GL implementation) 376 | QRhi::FrameOpResult r = m_r->beginFrame(m_sc); 377 | if (r == QRhi::FrameOpSwapChainOutOfDate) { 378 | resizeSwapChain(); 379 | if (!m_hasSwapChain) 380 | return; 381 | r = m_r->beginFrame(m_sc); 382 | } 383 | if (r != QRhi::FrameOpSuccess) { 384 | requestUpdate(); 385 | return; 386 | } 387 | 388 | m_frameCount += 1; 389 | if (m_timer.elapsed() > 1000) { 390 | if (rhiFlags.testFlag(QRhi::EnableProfiling)) { 391 | const QRhiProfiler::CpuTime ff = m_r->profiler()->frameToFrameTimes(m_sc); 392 | const QRhiProfiler::CpuTime be = m_r->profiler()->frameBuildTimes(m_sc); 393 | const QRhiProfiler::GpuTime gp = m_r->profiler()->gpuFrameTimes(m_sc); 394 | if (m_r->isFeatureSupported(QRhi::Timestamps)) { 395 | qDebug("ca. %d fps. " 396 | "frame-to-frame: min %lld max %lld avg %f. " 397 | "frame build: min %lld max %lld avg %f. " 398 | "gpu frame time: min %f max %f avg %f", 399 | m_frameCount, 400 | ff.minTime, ff.maxTime, ff.avgTime, 401 | be.minTime, be.maxTime, be.avgTime, 402 | gp.minTime, gp.maxTime, gp.avgTime); 403 | } else { 404 | qDebug("ca. %d fps. " 405 | "frame-to-frame: min %lld max %lld avg %f. " 406 | "frame build: min %lld max %lld avg %f. ", 407 | m_frameCount, 408 | ff.minTime, ff.maxTime, ff.avgTime, 409 | be.minTime, be.maxTime, be.avgTime); 410 | } 411 | } else { 412 | qDebug("ca. %d fps", m_frameCount); 413 | } 414 | 415 | m_timer.restart(); 416 | m_frameCount = 0; 417 | } 418 | 419 | customRender(); 420 | 421 | m_r->endFrame(m_sc, endFrameFlags); 422 | 423 | if (!scFlags.testFlag(QRhiSwapChain::NoVSync)) 424 | requestUpdate(); 425 | else // try prevent all delays when NoVSync 426 | QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); 427 | } 428 | 429 | int main(int argc, char **argv) 430 | { 431 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 432 | QGuiApplication app(argc, argv); 433 | 434 | // Defaults. 435 | #if defined(Q_OS_WIN) 436 | graphicsApi = D3D11; 437 | #elif defined(Q_OS_DARWIN) 438 | graphicsApi = Metal; 439 | #elif QT_CONFIG(vulkan) 440 | graphicsApi = Vulkan; 441 | #else 442 | graphicsApi = OpenGL; 443 | #endif 444 | 445 | // Allow overriding via the command line. 446 | QCommandLineParser cmdLineParser; 447 | cmdLineParser.addHelpOption(); 448 | QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); 449 | cmdLineParser.addOption(nullOption); 450 | QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)")); 451 | cmdLineParser.addOption(glOption); 452 | QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); 453 | cmdLineParser.addOption(vkOption); 454 | QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); 455 | cmdLineParser.addOption(d3dOption); 456 | QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); 457 | cmdLineParser.addOption(mtlOption); 458 | // Testing cleanup both with QWindow::close() (hitting X or Alt-F4) and 459 | // QCoreApplication::quit() (e.g. what a menu widget would do) is important. 460 | // Use this parameter for the latter. 461 | QCommandLineOption sdOption({ "s", "self-destruct" }, QLatin1String("Self destruct after 5 seconds")); 462 | cmdLineParser.addOption(sdOption); 463 | 464 | cmdLineParser.process(app); 465 | if (cmdLineParser.isSet(nullOption)) 466 | graphicsApi = Null; 467 | if (cmdLineParser.isSet(glOption)) 468 | graphicsApi = OpenGL; 469 | if (cmdLineParser.isSet(vkOption)) 470 | graphicsApi = Vulkan; 471 | if (cmdLineParser.isSet(d3dOption)) 472 | graphicsApi = D3D11; 473 | if (cmdLineParser.isSet(mtlOption)) 474 | graphicsApi = Metal; 475 | 476 | qDebug("Selected graphics API is %s", qPrintable(graphicsApiName())); 477 | qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText())); 478 | 479 | #ifdef EXAMPLEFW_PREINIT 480 | void preInit(); 481 | preInit(); 482 | #endif 483 | 484 | // OpenGL specifics. 485 | QSurfaceFormat fmt; 486 | fmt.setDepthBufferSize(24); 487 | fmt.setStencilBufferSize(8); 488 | if (sampleCount > 1) 489 | fmt.setSamples(sampleCount); 490 | if (scFlags.testFlag(QRhiSwapChain::NoVSync)) 491 | fmt.setSwapInterval(0); 492 | if (scFlags.testFlag(QRhiSwapChain::sRGB)) 493 | fmt.setColorSpace(QSurfaceFormat::sRGBColorSpace); 494 | QSurfaceFormat::setDefaultFormat(fmt); 495 | 496 | // Vulkan setup. 497 | #if QT_CONFIG(vulkan) 498 | QVulkanInstance inst; 499 | if (graphicsApi == Vulkan) { 500 | #ifndef Q_OS_ANDROID 501 | inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); 502 | #else 503 | inst.setLayers(QByteArrayList() 504 | << "VK_LAYER_GOOGLE_threading" 505 | << "VK_LAYER_LUNARG_parameter_validation" 506 | << "VK_LAYER_LUNARG_object_tracker" 507 | << "VK_LAYER_LUNARG_core_validation" 508 | << "VK_LAYER_LUNARG_image" 509 | << "VK_LAYER_LUNARG_swapchain" 510 | << "VK_LAYER_GOOGLE_unique_objects"); 511 | #endif 512 | inst.setExtensions(QByteArrayList() 513 | << "VK_KHR_get_physical_device_properties2"); 514 | if (!inst.create()) { 515 | qWarning("Failed to create Vulkan instance, switching to OpenGL"); 516 | graphicsApi = OpenGL; 517 | } 518 | } 519 | #endif 520 | 521 | // Create and show the window. 522 | Window w; 523 | #if QT_CONFIG(vulkan) 524 | if (graphicsApi == Vulkan) 525 | w.setVulkanInstance(&inst); 526 | #endif 527 | w.resize(1280, 720); 528 | w.setTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName()); 529 | w.show(); 530 | 531 | if (cmdLineParser.isSet(sdOption)) 532 | QTimer::singleShot(5000, qGuiApp, SLOT(quit())); 533 | 534 | int ret = app.exec(); 535 | 536 | // Window::event() will not get invoked when the 537 | // PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow 538 | // destruction. That happens only when exiting via app::quit() instead of 539 | // the more common QWindow::close(). Take care of it: if the 540 | // QPlatformWindow is still around (there was no close() yet), get rid of 541 | // the swapchain while it's not too late. 542 | if (w.handle()) 543 | w.releaseSwapChain(); 544 | 545 | return ret; 546 | } 547 | -------------------------------------------------------------------------------- /qwindow/imguidemo.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2018 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "examplefw.h" 52 | #include "qrhiimgui.h" 53 | 54 | static float vertexData[] = { 55 | 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 56 | -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 57 | 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 58 | }; 59 | 60 | struct { 61 | QVector releasePool; 62 | QRhiBuffer *vbuf = nullptr; 63 | QRhiBuffer *ubuf = nullptr; 64 | QRhiShaderResourceBindings *srb = nullptr; 65 | QRhiGraphicsPipeline *ps = nullptr; 66 | QRhiResourceUpdateBatch *initialUpdates = nullptr; 67 | float rotation = 0; 68 | QRhiImgui imgui; 69 | } d; 70 | 71 | void Window::customInit() 72 | { 73 | d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); 74 | d.vbuf->create(); 75 | d.releasePool << d.vbuf; 76 | 77 | d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); 78 | d.ubuf->create(); 79 | d.releasePool << d.ubuf; 80 | 81 | d.initialUpdates = m_r->nextResourceUpdateBatch(); 82 | d.initialUpdates->uploadStaticBuffer(d.vbuf, vertexData); 83 | float opacity = 1.0f; 84 | d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &opacity); 85 | 86 | d.srb = m_r->newShaderResourceBindings(); 87 | d.releasePool << d.srb; 88 | d.srb->setBindings({ 89 | QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf), 90 | }); 91 | d.srb->create(); 92 | 93 | d.ps = m_r->newGraphicsPipeline(); 94 | d.releasePool << d.ps; 95 | 96 | QShader vs = getShader(QLatin1String(":/color.vert.qsb")); 97 | Q_ASSERT(vs.isValid()); 98 | QShader fs = getShader(QLatin1String(":/color.frag.qsb")); 99 | Q_ASSERT(fs.isValid()); 100 | d.ps->setShaderStages({ 101 | { QRhiShaderStage::Vertex, vs }, 102 | { QRhiShaderStage::Fragment, fs } 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 | d.ps->setVertexInputLayout(inputLayout); 115 | d.ps->setShaderResourceBindings(d.srb); 116 | d.ps->setRenderPassDescriptor(m_rp); 117 | 118 | d.ps->create(); 119 | 120 | d.imgui.initialize(m_r); 121 | d.imgui.setInputEventSource(this); 122 | d.imgui.setFrameFunc([] { 123 | d.imgui.demoWindow(); 124 | }); 125 | } 126 | 127 | void Window::customRelease() 128 | { 129 | d.imgui.releaseResources(); 130 | 131 | qDeleteAll(d.releasePool); 132 | d.releasePool.clear(); 133 | } 134 | 135 | void Window::customRender() 136 | { 137 | const QSize outputSizeInPixels = m_sc->currentPixelSize(); 138 | QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); 139 | QRhiRenderTarget *rt = m_sc->currentFrameRenderTarget(); 140 | QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch(); 141 | 142 | if (d.initialUpdates) { 143 | u->merge(d.initialUpdates); 144 | d.initialUpdates->release(); 145 | d.initialUpdates = nullptr; 146 | } 147 | 148 | QMatrix4x4 mvp = m_proj; 149 | d.rotation += 1; 150 | mvp.rotate(d.rotation, 0, 1, 0); 151 | u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData()); 152 | 153 | d.imgui.prepareFrame(rt, m_rp, u); 154 | 155 | cb->beginPass(rt, QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f), { 1.0f, 0 }, u); 156 | 157 | cb->setGraphicsPipeline(d.ps); 158 | cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height())); 159 | cb->setShaderResources(); 160 | const QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0); 161 | cb->setVertexInput(0, 1, &vbufBinding); 162 | cb->draw(3); 163 | 164 | d.imgui.queueFrame(cb); 165 | cb->endPass(); 166 | } 167 | -------------------------------------------------------------------------------- /qwindow/imguidemo.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../color.vert.qsb 4 | ../color.frag.qsb 5 | ../imgui.vert.qsb 6 | ../imgui.frag.qsb 7 | 8 | 9 | -------------------------------------------------------------------------------- /qwindow/qwindow.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += gui-private 4 | 5 | SOURCES = \ 6 | imguidemo.cpp \ 7 | ../qrhiimgui.cpp \ 8 | ../imgui/imgui.cpp \ 9 | ../imgui/imgui_draw.cpp \ 10 | ../imgui/imgui_widgets.cpp \ 11 | ../imgui/imgui_demo.cpp 12 | 13 | HEADERS = \ 14 | ../qrhiimgui.h \ 15 | ../qrhiimgui_p.h 16 | 17 | INCLUDEPATH += .. ../imgui 18 | 19 | RESOURCES = \ 20 | imguidemo.qrc 21 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpqr/qrhiimgui/b737d519ca2702c6ab9fe1b431048c3e53774a3a/screenshot.png --------------------------------------------------------------------------------