diff --git a/3rdparty/imgui/CHANGELOG.txt b/3rdparty/imgui/CHANGELOG.txt index 6b9ad6b086..c99d0f0473 100644 --- a/3rdparty/imgui/CHANGELOG.txt +++ b/3rdparty/imgui/CHANGELOG.txt @@ -39,6 +39,418 @@ HOW TO UPDATE? and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users. - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.93.0 (Released 2026-10-14) +----------------------------------------------------------------------- + +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.93.0 + +In 1.93.0, the `ImDrawList` rendering API has been improved to be more robust and +more consistent over the whole range of shapes. The rendering now matches much closer +to common vector drawing APIs, like html canvas. Improvements, like anti-aliased line +endings and stroke position were added. The coordinates passed to the API are now +consistently describing shape outlines. + +For code using custom rendering: some details are now rendered slightly differently +(even if generally more consistent). The difference in rendering is in the range of +half a pixel, but that can lead to issues like some lines now look blurrier or more +transparent. All Dear ImGui widgets should appear identical or better. + +The rendering of common shapes was optimized when the shapes fall on integer coordinates +(which is most of the UI). This includes skipping anti-aliasing when not needed, or +using textures instead of tessellated geometry to draw round corners. +The varying code-paths are designed to look indistinguishable from each others. + +New rendering code fixes some inconsistencies between graphics APIs +(e.g. DirectX vs OpenGL) which are using different diamond exits rules. +New code should work precisely the same regardless of which graphics API is used +(note that using _StrokeLegacy mode can lead to the same old inconsistencies). + +New rendering code also deals better with animating thickness and rounding +(integer values are recommended). + +While the current version doesn't make lots of visible use of the new features and +facilities, they are among the pillars that will make it easier to create prettier +contents, both for custom rendering and future versions of the library. + +Use `ImDrawFlags_StrokeLegacy` mode to emulate old rendering: +- Per-primitive: draw_list->AddLine(..., ..., ImDrawFlags_StrokeLegacy); +- For a given scope: draw_list->PushDrawFlag(ImDrawFlags_StrokeLegacy, true); draw_list->AddLine(...); draw_list->PopDrawFlag(); +- Globally: ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); // Map to a checkbox +- Globally: io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on Shift + +Don't panic! +Many applications will not notice any difference! You will more likely see +differences if you used lots of direct `ImDrawList` calls for custom rendering. + +Credits for ImDrawList changes in 1.93.0: +- main work courtesy of @memononen. +- additional work/reviewing/guidance by @ocornut. +- with legacy bits from @thedmd, @potocpav. +Other credits marked below, or @ocornut when omitted. + +Refer to our guides: +- ImDrawList Vector Rendering Reference + https://github.com/ocornut/imgui/wiki/Draw-List + (includes a precise list of differences between 1.92.9 and 1.93.0). +- ImDrawList 1.92.9 vs 1.93.0 Interactive Testbed + https://www.dearimgui.com/docs/drawlist_v193 + (interactively compare differences between 1.92.9 and 1.93.0 + commentary) + (downloadable HTML file, may be used offline) +- Pixel Perfect Anti-aliased Rendering + https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering +- How Anti-aliased Polyline Rendering is Implemented + https://github.com/ocornut/imgui/wiki/Polyline-Rendering +- Discussion topic: + https://github.com/ocornut/imgui/issues/9504 + (if you have a problem to discuss, please open a new issue) + +Breaking Changes: + +- ImDrawList: + - AddLine(): removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates. (#9504) + - This fixes inconsistencies in the API and matches the PathXXX API. + - By default, stroke thickness extends on both side of the given segment. + e.g for a "pixel-perfect" looking line with thickness=1.0f, coords should be passed as center of each ends of the line. + - Use `ImDrawFlags_StrokeLegacy` to use old offset if required. Or you can apply the offset manually! + - Generally better, simple and faster to use `AddLineH()`, `AddLineV()` functions added in 1.92.8. + READ IF YOU ARE MINDFUL OF PIXEL-PERFECTNESS IN YOUR CUSTOM RENDERING/WIDGETS: + - TRANSITION GUIDE FOR AXIS-ALIGNED LINES: + - When switching from legacy `AddLine()` values to `AddLineV()`, `AddLineH()` you can keep same inputs coordinates as before: + - Old `AddLine({x, y1}, {x, y2}, col)` --> `AddLineV(x, y1, y2, col);` // Vertical line. + - Old `AddLine({x1, y}, {x2, y}, col)` --> `AddLineH(x1, x2, y, col);` // Horizontal line. + - This will be equivalent and faster for thickness=1.0f lines. + - Since `AddLine()` use default stroke pos Center and `AddLineV()`, `AddLineH()` use Inside, thickness>1.0f strokes will differ. + Using `ImDrawFlags_StrokeCenter` will match old result more closely (aka look centered), + but is more likely to look blurry as it already did before. + - Read table below about StrokePos and the "Pixel-Perfect Rendering" guide if you care about pixel-perfect lines. + - TRANSITION GUIDE FOR DIAGONAL LINES: + - Your lines will appear offset by -0.5f pixels on each axis. + Being anti-aliased diagonal lines, they won't look particularly better or worse, just slightly offset. + It'll likely only be noticeable if you carefully combined them with other primitives for a pixel-perfect result. + - Old `AddLine({x1, y1}, {x2, y2}, col)` --> `AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col);` // Same + or use `ImDrawFlags_StrokeLegacy` --> `AddLine({x1, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy);` // Offset by +0.5f + disable AA ends. + This reapplies the old offset, and should get you exactly the same result you previously + got for thickness=1.0f lines. But said result were previously sometimes ambiguous and + renderer-dependent. (#3116, #3258, #2441) + - Old code did not support anti-aliased ends, which could create gaps when rendering shapes out + of multiple contiguous lines instead of using `AddPolyline()`. There is a possibility that you + legacy code added fudge offsets here and there; which may not be necessary anymore if you use + `_AALineEnds`, or if you combine rendering of contiguous lines using `AddPolyline()`. + - AddRect(), AddCircle(), AddNgon(), AddEllipse(), AddTriangle(), AddQuad(): defaulting to "inside" stroke. + - All closed shapes with thickness=1.0f will appear identical. + - The difference for thickness>1.0f shapes may be minimal since very large strokes + were not well supported for widgets, but stroke will default inside widgets. + - AddCircle(), AddNgon(): removed -0.5f offset added to radius. + - This fixes inconsistencies in the API. + - Use `ImDrawFlags_StrokeLegacy` to use old method if required. Or you can apply the offset manually! + - AddRectFilled(): non-integer coordinates will now display anti-aliased edges. + Previously, non-integer coordinates rendered with aliased edges snapped by the + rasterizer. (#6971) + - AddRect(), AddRectFilled(): rectangles with inverted coordinates won't be visible + unless using `ImDrawFlags_StrokeLegacy` mode. With inverted coordinates (min > max): + - Legacy `AddRect()` rounding off -> visible but incorrect outer size. + - Legacy `AddRect()` rounding on -> visible but very glitchy. + - Legacy `AddRectFilled()` rounding off -> visible and correct. + - Legacy `AddRectFilled()` rounding on -> visible but very glitchy. + - Use `ImDrawFlags_StrokeLegacy` to use old rendering for cases without rounding, or fix coordinates. + - Merged `ImDrawListFlags` into `ImDrawFlags`. `ImDrawListFlags` was rarely used directly + and generally setup by the ImGui context. + - `ImDrawListFlags_AntiAliasedLines` -> `ImDrawFlags_AALines` + - `ImDrawListFlags_AntiAliasedFill` -> `ImDrawFlags_AAFill` + - `ImDrawListFlags_AllowVtxOffset` -> `ImDrawFlags_UseVtxOffset` + - `ImDrawListFlags_TextNoPixelSnap` -> `ImDrawFlags_TextNoPixelSnap` + Unifying them allows easily using them for both per-primitives and scope alterations. + Kept redirection enums (will obsolete). + - Renamed all `ImDrawFlags_RoundCornersXXX` flags to `ImDrawFlags_RoundXXX`. + - `ImDrawFlags_RoundCornersAll` -> `ImDrawFlags_RoundAll` + - `ImDrawFlags_RoundCornersNone` -> `ImDrawFlags_RoundNone` + - `ImDrawFlags_RoundCornersTopLeft` -> `ImDrawFlags_RoundTopLeft` + - `ImDrawFlags_RoundCornersTopRight` -> `ImDrawFlags_RoundTopRight` + - `ImDrawFlags_RoundCornersBottomLeft` -> `ImDrawFlags_RoundBottomLeft` + - `ImDrawFlags_RoundCornersBottomRight` -> `ImDrawFlags_RoundBottomRight` + - `ImDrawFlags_RoundCornersTop` -> `ImDrawFlags_RoundTop` + - `ImDrawFlags_RoundCornersBottom` -> `ImDrawFlags_RoundBottom` + - `ImDrawFlags_RoundCornersLeft` -> `ImDrawFlags_RoundLeft` + - `ImDrawFlags_RoundCornersRight` -> `ImDrawFlags_RoundRight` + Kept redirection enums (will obsolete). + - Marked `ImDrawListFlags_AntiAliasedLinesUseTex` and `style.AntiAliasedLinesUseTex` as + obsolete, as the new line rendering code always uses textures. + - Enabling `ImFontAtlasFlags_NoBakedLines` in the font atlas will now disable support for + anti-aliased lines by clearing `style.AntiAliasedLines`/`style.AntiAliasedLineEnds`. +- Style: obsoleted `style.CurveTessellationTol` (default 1.25) which was in Pixels² unit in + favor of `style.CurveTessellationMaxError` (default 1.12) which is in Pixels unit. It is + easier to tweak + this allows us to easily scale error threshold on high density screens. + - style.CurveTessellationMaxError == sqrtf(style.CurveTessellationTol). +- ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: + prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. +- Fonts: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). + (#609, #5748, #9559) +- Backends: + - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was + obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support + for `ImGuiBackendFlags_RendererHasTextures`. Use the new signature! (#9487) + - OpenGL3: Does not attempt to read `GL_CLIP_ORIGIN` by default as the detection is + sub-optimal and not reliable on some setup. Use `ImGui_ImplOpenGL3_SetClipOrigin(0)` + to enable auto-detection, or e.g. `ImGui_ImplOpenGL3_SetClipOrigin(GL_UPPER_LEFT)`. + +Other Changes: + +- Style: + - StyleColorsDark(): minor refresh of the default style to look more modern: + - Changed WindowBg to use a slightly greyer value and removed opacity. + - Changed FrameBg values to use a dark black to make frames stand out better. + - Tweak TextDisabled, TableHeaderBg, TableBorders to match altered WindowBg. + - StyleColorsLight(): inverted `ImGuiCol_TitleBgXXX` colors so that the selected + window title bar appears brighter. +- ImDrawList: + - Added flags to specify stroke position in all `ImDrawList` stroking functions: (#9504) [@memononen] + - `ImDrawFlags_StrokeInside` (default for Rect, Circle/Ellipse, Triangle, Quad, Ngon + AddLineH, AddLineV) + - `ImDrawFlags_StrokeCenter` (default for Line, Polyline, Beziers, Paths function) + - `ImDrawFlags_StrokeCenterBiased` (center w/ bias for pixel perfect alignment) + - `ImDrawFlags_StrokeOutside` + - `ImDrawFlags_StrokeLegacy` (match old behavior) + - Legacy mode may be set as default for a given scope using `PushDrawFlag(ImDrawFlags_StrokeLegacy, true)`. + - Legacy code was generally applying +0.50f offsets which meant that, + - with thickness=1.0f: the stroke would appear inside. + - with thickness>1.0f: the stroke would start expanding on both sides but starting from that small initial offset. + - with thickness>1.0f for even integer values, the stroke would look blurry. + - TL;DR; the logic didn't make much sense for thickness>1.0f. + - Defaulting to Inside for closed shapes ensure that rectangles and lines are + never blurry, regardless of thickness, as long as input coordinates/sizes + are integers. (#9359) Recap: + ------------------------------------------------------------------------ + Thickness Legacy Inside Outside Center CenterBiased + ------------------------------------------------------------------------ + 1.0f sharp sharp sharp blurry sharp + 2.0f blurry sharp sharp sharp sharp + 3.0f sharp sharp sharp blurry sharp + ------------------------------------------------------------------------ + - Read our Wiki guides for more details! + https://github.com/ocornut/imgui/wiki/Draw-List + - And interactive 1.92.9 vs 1.93.0 comparing testbed: + https://www.dearimgui.com/docs/drawlist_v193 + - AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. + (#9504, #2183, #2964, #7972 + #9360, #6331, #4250, #4091) [@memononen] + - Generally fixed rendering of thick strokes/paths. + - The new stroke expansion now uses corner miter calculation, which keeps thickness along + the line segments consistent. If the corner becomes too sharp, it will be beveled to + avoid long spikes at corners. + - There are also some robustness measures for the case where the line thickness is larger + than the features being drawn. + - Rendering artifacts are still possible when the path features are smaller than line thickness, + but the artifacts should be more localized. The new implementation attempted to favor simple + implementation and robustness over the corner case accuracy. + - All functions: new rendering code fixes some inconsistencies between graphics APIs + (e.g. DirectX vs OpenGL) which are using different diamond exits rules. New code should + work precisely the same regardless of graphics API. (#3116, #3258, #2441) [@memononen] + - Debug: added `io.ConfigDebugDrawListDefaultsToStrokeLegacy` to facilitate comparing old/new + behaviors. This is equivalent to calling `PushDrawFlag(ImDrawFlags_StrokeLegacy, true)` + on every drawlists. + Suggested uses, e.g. + - `io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on holding Shift` + - `ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy);` + - Added `ImDrawFlags_AALineEnds` flag to enable anti-aliased line ends. + - Most noticeable for thick lines. + - Also helps fill minor gaps if combining multiple individual lines. For best looking + continuous lines you should use AddPolyline(), it creates joins between line segments + to avoid any gaps. + - Added `style.AntiAliasedLineEnds` in ImGui to set default state. + - This is disabled by default! Enabling AA Ends by default would be more "correct", + but since it is very slightly slower and increases vertex/index data we decided + to not enable it by default. + This keeps situations such as "throw tens of thousands of lines at ImDrawList" + optimal. You can opt-in for a given shape or in a scope using `PushDrawFlag()`. + - Added `ImDrawFlags_JoinMiter` flag to disable using beveled corner. + - Closer to legacy rendering and slightly faster, but corners sharper than + 90 degrees may expand far out. + - Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() functions + since we know that the input path does not contain sharper corners. + - Added `ImDrawFlags_CapSquare` flag to extend lines ends using square caps. (#9360) + - AddConvexPolyFilled()/PathFillConvex(): improved to handle input geometry more robustly. + The rendering of sharp corners was improved. Earlier there were issues with sharp corners + either creating long spikes, or messing up the adjacent edge's anti-aliasing. Now the + anti-aliasing fringe calculation is more consistent, and sharp corners get beveled to + avoid spikes. There might still be issues when the smallest extent of a long thin polygon + is less than a pixel. Also supports `ImDrawFlags_JoinMiter`, to facilitate matching + a stroke using the same flag. + - `AddRect()`, `AddRectFilled()`: various optimizations for rounded rectangles. (#1962, #9504) + - Integer-aligned coordinates and thicknesses will automatically use baked textures, + saving on both CPU and vertex/index data. + - Enabled by default. Use `ImFontAtlasFlags_NoBakedRoundCorners` to disable baking + round corners in font atlas. + - Added `ImDrawFlags_UseTexForRoundCorners` flag to enable in scope or per-shape. + Added for consistency and debugging, not really needed in practice. + - `AddCircle()`, `AddCircleFilled()`: fixed large circles being under-tessellated + (when above ~140 with default CircleTessellationMaxError). + - Improved debug-build performance in various locations. + - Improved minor mismatches when overlapping strokes and filled shapes. + For example, when using inside strokes, + - `AddRect()` and `AddRectFilled()` with rounding now overlap better. (#3656) + - `AddCircle()` and `AddCircleFilled()` now overlap better. + - Added `ImDrawList::PushDrawFlag()`/`PopDrawFlag()` to alter certain flags for a + scope and a given `ImDrawList`. (#9489) + - e.g. `PushDrawFlag(ImDrawFlags_StrokeLegacy, true);` enforce legacy tweaks. + - About `ImDrawFlags_StrokeLegacy`: + - This is designed to emulate old behavior: + - Most shapes use `ImDrawFlags_StrokeCenter`. + - AddLine() add +0.5f,+0.5f offset to position. + - AddRect() and AddRectFilled() allow inverted positions. + - AddRectFilled() has no AA when passed non-integer coordinates. + - AddCircle(), AddNgon() add a -0.5f offset to radius. + - Closed shapes use miter corners. + - Lines don't have anti-aliased ends. + - But there are known difference between legacy code and ImDrawFlags_StrokeLegacy: + - Thick shapes with acute angles will now preserve thickness better, + which may make them appear as protruding a little more. + - About disabling anti-aliasing: + - Disabling anti-aliasing is now emulated by applying different UV coordinates. + - It does not result in a performance increase or vertex/index data decrease any more + (however the tendency is that the new code behaves better than old code). + - But may be a useful stylistic choice in some circumstances. + - Many thick shapes had broken corners with old no-AA code, and the old no-AA code + was wildly different from the AA code. Not the case any more. (#288) + - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: + - AA Fringe is scaled accordingly. (#9577) + - Circle and Curves tessellation error are scaled accordingly. + - Reworked assert in `PushTexture()` to allow convenience grace period of using a + texture that was queue for destroy during the frame. Make it safe to stick to + an existing texture during the frame. (#9528, #8465) + - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic + draw callback for requesting to use the sampler associated already associated to a texture. + Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) + - Fixed dangling `ImDrawList::_OwnerName` when using animated label. (#9552) [@ubi-dcastonguay] +- Mixed Values: + - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values. + Supported by: Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), ColorXXX() + and Combo() functions. + The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865) +- Drags, Sliders: + - Ongoing drags may be cancelled using Right-Click, Escape or Gamepad Triangle. + Cancelling reverts to the initial value. (#8564, #9534) + - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which + leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] +- TreeNode: + - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX + features or other features requiring stack storage. (#9509) [@lasrod] +- TabBar: + - Improved scroll target when clicking a Tab that is larger than the scrolling + section width. + - Using <> scrolling buttons while the selected Tab is at either end of the + section mid-section but not fully visible always makes it fully visible + (before eventually selecting a Tab in the leading/trailing section). +- Tables: + - Fixed a bug restoring correct .ini columns order when table use is Sortable + but had no user changes. (regression from 1.92.9). (#9519, #9108) + - Fixed a bug tracking topology changes (insert new columns in the middle) + when the table is not _Reorderable: new columns would be ordered incorrectly. + (#9570, #9108, #9572) [@lailoken] +- InputText: + - Fixed a crash in some cases when using Home key in a WordWrap+ReadOnly field. (#9561) +- Selectable: + - Fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. +- Fonts: + - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic + to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of + ProggyClean on most Apple/Retina setups by default. + - Added `ImFont::AddRemapCodepointToGlyphIndex()` allowing to access glyphs that are not + mapped to codepoints. (#9559, #5647, #609, #5748, #8466) + - imgui_freetype: Fixed blurriness rendering `AddFontDefaultVector()`. Caused by a + non-pixel aligned offset caused by `ExtraSizeScale`. (#9348, #9233) [@redpartizan, @RDMCz] + - (Internal) ImFontLoader: reworked `FontSrcContainsGlyph`, `FontBakedLoadGlyph` signatures + to take raw glyph index instead of codepoint. Added `FontSrcGetGlyphIndexFromCodepoint`. + (#9559, #5647, #9540, #8466, #8465) + - Added `IMGUI_DISABLE_STB_TRUETYPE` to completely omit the stb_truetype loader + code + stb_truetype.h code. (#9573) +- ColorEdit: + - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by `BeginDisabled()` so + disabled color buttons have the same color as non-disabled oness. (#9511) + - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining + color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] +- Popups: + - Amended context menu right-click opening code to check that the right-click + hasn't been owned by another items. This is more correct and also necessary + to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) + - Reworked `GetMousePosOnOpeningCurrentPopup()` to be legal to call within the + BeginPopup/EndPopup scope of a popup which was just closed. (#9551) [@jkunstwald] +- ImGuiTextFilter: + - Added support for space as an "and" filter, which is the standard. (#2435, #30) + e.g. w/ filter 'hello world' (without quotes) both words needs to be in input data. + - Added support for double-quoted "xxx" blocks to e.g. match words sequences. (#2435) + e.g. w/ filter "hello world" the exact sequence needs to be in input data. + - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" + match always filters out. (#2435) [@kudaba, @ocornut] + - Added `DrawWithHint()` function. (#6206, #6395, #6447) + - Added a "incl -excl" hint by default when the search field is empty. (#6206, #6395, #6447) +- Widgets: + - Checkbox, Menus: check marks use anti-aliased ends. + - Tweaked rendering in various locations to avoid blurriness: + - Windows: title-bar and menu-bar border (when thickness>1.0f). + - SeparatorText: border (when thickness>1.0f). + - Tree: hierarchy lines (when thickness>1.0f). + - InputText: input caret (when thickness>1.0f). + - TabBar: selected overline border (when thickness>1.0f). +- Misc: + - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset + every frame when accumulated session time is very large (e.g. a few days). [@lailoken] + - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) + or `[[gnu::artificial]]` (Clang/GCC) in order to provide a hint to debuggers to skip + trivial functions when stepping into. + - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. + and various operators. + - Fixed strict aliasing violation in `ImHashData()` affecting Clang 23 + SSE 4.2 + -flto. + Could manifest as `ImFontAtlasBakedGetId()` computation omitting font size from + the hashed data. (#9557, #9560) [@sthalik] + - IO: restore default clipboard/ime/shell platform handlers on `ClearPlatformHandlers()`, fixing + issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] +- Demo: + - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) + - Custom Rendering section: exposed new flags; showcasing overlapping strokes + and filled shapes; added an option to animate thickness; added new shapes + to showcase new rendering features that previously hit limitations. + - Style Editor: fixed style/colors combo box applying style when opening Combo. +- Backends: + - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - Metal, Metal4: added support for rendering inside a render pass with multiple color + attachments (e.g. when merged with other passes): we render to the first color attachment + and leave the other ones untouched. (#9574) [@tqjxlm] + - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw + callback to request using the sampler associated already associated to a texture. (#9378) + - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not resetting all states + in the glTexParameter() path. (#9378) + - OpenGL3: added `ImGui_ImplOpenGL3_SetClipOrigin()` to specify origin between + `GL_LOWER_LEFT` (new default), `GL_UPPER_LEFT` and `0` (autodetect, old default). + - OpenGL3: avoid querying `glGetIntegerv(GL_CLIP_ORIGIN)` multiple times when using + when using default and using `DrawCallback_ResetRenderState`, at it appear to stall + some drivers. (#2186, #3998, #3146, #3143, #8640) + - SDL2: fixed querying framebuffer scale/density when using Metal without + going through a SDL_Renderer. (#5592) [@lailoken] + - SDLRenderer3: restore any temporarily modified texture scale modes at the of `RenderDrawData()`. (#9378) + - SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543, #9378) [@Clownacy] + - SDLRenderer3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw callback to request + using the sampler associated already associated to a texture via `SDL_SetTextureScaleMode()`. (#9378) + It is equivalent to setting `render_state->CurrentScaleMode = SDL_SCALEMODE_INVALID`. + Note: this callback is only supported by selected rendering backends: most other graphics API + don't have the equivalent of `SDL_SetTextureScaleMode()` and the callback won't work with them. + - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] + - Vulkan: added support for rendering inside a render pass with multiple color attachments + (e.g. when merged with other passes): we render to the first color attachment and leave the + other ones untouched. (#9574) [@tqjxlm] + - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` + when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] + - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] +- Examples: + - All: all examples now default to use a little bit of rounding (`FrameRounding` and `GrabRounding`). + - MSVC: disabled /JMC in Visual Studio projects (unnecessary overhead). + - GLFW/SDL2/SDL3+WebGPU: fixed running with wgpu-native missing `wgpuInstanceWaitAny()`. (#9377) + - GLFW/SDL2/SDL3+WebGPU: better select surface format. (#9576) [@ypujante] + - GLFW+Metal: fixed mistakenly calling `ImGui_ImplGlfw_InitForOpenGL()`, which + broke multi-viewport support. (#9564) [@ypujante] + - SDL2+Metal: create Metal context without using a `SDL_Renderer`. + - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] + - OSX+OpenGL3: added example_apple_opengl3/ (OSX/Cocoa + OpenGL 3.2 Core profile). + + ----------------------------------------------------------------------- VERSION 1.92.9b (Released 2026-07-31) ----------------------------------------------------------------------- @@ -1639,7 +2051,7 @@ Non-breaking Fonts/Textures related changes: - Fonts: ImFontAtlas::AddFontXXX() can fail more gracefully if error handling is configured to not assert (this will be better exposed via future font flags). - Fonts: added style.FontScaleBase scaling factor (previously called io.FontGlobalScale). -- Fonts: added style.FontScaleDpi scaling factor. This is designed to be be changed on +- Fonts: added style.FontScaleDpi scaling factor. This is designed to be changed on per-monitor/per-viewport basis, which `io.ConfigDpiScaleFonts` does automatically. (which is why it is separate from FontScaleBase). - Fonts: added optional font_size parameter to ImGui::PushFont() function. @@ -2794,7 +3206,7 @@ Other changes: - Backends: Vulkan: Remove Volk/ from volk.h #include directives. (#7722, #6582, #4854) [@martin-ejdestig] - Examples: SDL3: Remove use of SDL_HINT_IME_NATIVE_UI since new SDL_HINT_IME_IMPLEMENTED_UI - values has a more suitable default for our case case. + values has a more suitable default for our case. - Examples: GLFW+Vulkan, SDL+Vulkan: handle swap chain resize even without Vulkan returning VK_SUBOPTIMAL_KHR, which doesn't seem to happen on Wayland. (#7671) [@AndreiNego, @ocornut] @@ -4747,7 +5159,7 @@ Other Changes: - ColorPicker4: Fixed picker being unable to select exact 1.0f color when dragging toward the edges of the SV square (previously picked 0.999989986f). (#3517) [@rokups] - Menus: Fixed vertical alignments of MenuItem() calls within a menu bar (broken in 1.84). (#4538) -- Menus: Improve closing logic when moving diagonally in empty between between parent and child menus to +- Menus: Improve closing logic when moving diagonally in empty space between parent and child menus to accommodate for varying font size and dpi. - Menus: Fixed crash when navigating left inside a child window inside a sub-menu. (#4510). - Menus: Fixed an assertion happening in some situations when closing nested menus (broken in 1.83). (#4640) diff --git a/3rdparty/imgui/include/imconfig.h b/3rdparty/imgui/include/imconfig.h index a758c6d211..943cce349b 100644 --- a/3rdparty/imgui/include/imconfig.h +++ b/3rdparty/imgui/include/imconfig.h @@ -72,7 +72,8 @@ //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) #define IMGUI_USE_WCHAR32 -//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version +//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version. +// This is for if you already embed a copy of those library in your code and don't want duplicate code in your binary. // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" @@ -102,20 +103,24 @@ //---- Use stb_truetype to build and rasterize the font atlas (default) // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. //#define IMGUI_ENABLE_STB_TRUETYPE +//#define IMGUI_DISABLE_STB_TRUETYPE //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. // This will be inlined as part of ImVec2 and ImVec4 class declarations. /* #define IM_VEC2_CLASS_EXTRA \ - constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ - operator MyVec2() const { return MyVec2(x,y); } + IM_NODEBUGSTEP constexpr inline ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ + IM_NODEBUGSTEP inline operator MyVec2() const { return MyVec2(x,y); } -#define IM_VEC4_CLASS_EXTRA \ - constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ - operator MyVec4() const { return MyVec4(x,y,z,w); } +#define IM_VEC4_CLASS_EXTRA \ + IM_NODEBUGSTEP constexpr inline ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ + IM_NODEBUGSTEP inline operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//---- ...Or use Dear ImGui's own very basic math operators. +//---- ...Or use Dear ImGui's own basic math operators in every file including imgui.h: +//#ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS +//#endif +//---- ...Or manually '#define IMGUI_DEFINE_MATH_OPERATORS' before '#include "imgui.h"' to use from one file. //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). diff --git a/3rdparty/imgui/include/imgui.h b/3rdparty/imgui/include/imgui.h index 05fc3309ee..a8310674a5 100644 --- a/3rdparty/imgui/include/imgui.h +++ b/3rdparty/imgui/include/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (headers) // Help: @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.9b" -#define IMGUI_VERSION_NUM 19291 +#define IMGUI_VERSION "1.93.0" +#define IMGUI_VERSION_NUM 19300 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -49,7 +49,7 @@ Index of this file: // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload) // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) -// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) // [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) @@ -128,6 +128,16 @@ Index of this file: #define IM_MSVC_RUNTIME_CHECKS_RESTORE #endif +// Alternative to using a .natstepfilter file or other scripts in misc/debuggers/ to skip debug-stepping selected trivial functions. +// If you get a compiler error or warning related to use, please report it to us! +#if (defined(__clang__) && (__clang_major__ >= 7)) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) +#define IM_NODEBUGSTEP [[gnu::artificial]] +#elif defined(_MSC_VER) && (_MSC_VER >= 1915) +#define IM_NODEBUGSTEP __declspec(non_user_code) +#else +#define IM_NODEBUGSTEP +#endif + // Warnings #ifdef _MSC_VER #pragma warning (push) @@ -236,7 +246,6 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. // - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions -typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance typedef int ImDrawTextFlags; // -> enum ImDrawTextFlags_ // Internal, do not use! typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas @@ -294,11 +303,11 @@ typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { - float x, y; - constexpr ImVec2() : x(0.0f), y(0.0f) { } - constexpr ImVec2(float _x, float _y) : x(_x), y(_y) { } - float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(void*)(char*)this)[idx]; } // We very rarely use this [] operator, so the assert overhead is fine. - float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const void*)(const char*)this)[idx]; } + float x, y; + IM_NODEBUGSTEP constexpr inline ImVec2() : x(0.0f), y(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec2(float _x, float _y) : x(_x), y(_y) { } + float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(void*)(char*)this)[idx]; } // We very rarely use this [] operator, so the assert overhead is fine. + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const void*)(const char*)this)[idx]; } #ifdef IM_VEC2_CLASS_EXTRA IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. #endif @@ -307,9 +316,9 @@ struct ImVec2 // ImVec4: 4D vector used to store clipping rectangles, colors etc. [Compile-time configurable type] struct ImVec4 { - float x, y, z, w; - constexpr ImVec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { } - constexpr ImVec4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) { } + float x, y, z, w; + IM_NODEBUGSTEP constexpr inline ImVec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) { } #ifdef IM_VEC4_CLASS_EXTRA IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4. #endif @@ -983,7 +992,7 @@ namespace ImGui IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! IMGUI_API const ImGuiPayload* GetDragDropPayload(); // peek directly into the current payload from anywhere. returns NULL when drag and drop is finished or inactive. use ImGuiPayload::IsDataType() to test for the payload type. - // Disabling [BETA API] + // Disabling // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) // - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled. @@ -1100,7 +1109,7 @@ namespace ImGui // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. // - The return value of SetItemKeyOwner() says if ownership has been requested for the item, which is a shortcut to calling yet non-public TestKeyOwner() function. // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. - IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. @@ -1261,6 +1270,17 @@ enum ImGuiItemFlags_ ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // false // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, + + //--------------------------------------------------------------------------------- + // [BETA] MixedValue mode used to represent a mixed/indeterminate state, typically for multi-selection. + // - Replace value display with "-" or a custom label. + // - Enter key validation apply an edit and return true even if value hasn't changed (in order to apply to all). + // - Supported by selected widgets: Checkbox, RadioButton, Sliders, Drags, Inputs, Combo. + // - Note: InputText-side Undo cannot be reliably combined with MixedValue + LiveEdit On: + // - Both the initial edit and subsequent undo/revert will typically make your app code write to all backing objects. + // - If you use MixedMode and the simplest solution is to ensure LiveEdit is off but widgets where this applies. + //--------------------------------------------------------------------------------- + ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. }; // Flags for ImGui::InputText() @@ -2259,13 +2279,13 @@ struct ImVector inline int size_in_bytes() const { return Size * (int)sizeof(T); } inline int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } inline int capacity() const { return Capacity; } - inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } - inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + IM_NODEBUGSTEP inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + IM_NODEBUGSTEP inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } - inline T* begin() { return Data; } - inline const T* begin() const { return Data; } - inline T* end() { return Data + Size; } - inline const T* end() const { return Data + Size; } + IM_NODEBUGSTEP inline T* begin() { return Data; } + IM_NODEBUGSTEP inline const T* begin() const { return Data; } + IM_NODEBUGSTEP inline T* end() { return Data + Size; } + IM_NODEBUGSTEP inline const T* end() const { return Data + Size; } inline T& front() { IM_ASSERT(Size > 0); return Data[0]; } inline const T& front() const { IM_ASSERT(Size > 0); return Data[0]; } inline T& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } @@ -2356,7 +2376,7 @@ struct ImGuiStyle ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. - float MenuItemRounding; // Radius of MenuItem, BeginMenu rounding. + float MenuItemRounding; // Radius of MenuItem, BeginMenu rounding. float SelectableRounding; // Radius of Selectable rounding. MODIFYING THIS IS DISCOURAGED. CONTIGUOUS SELECTIONS WILL NOT LOOK RIGHT. (#7589) float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding. float DragDropTargetBorderSize; // Thickness of the drag and drop target border. @@ -2373,10 +2393,12 @@ struct ImGuiStyle ImVec2 DisplayWindowPadding; // Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen. ImVec2 DisplaySafeAreaPadding; // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured). float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. - bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). - bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). - bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). - float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + // Rendering & Tessellation + bool AntiAliasedLines; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. + bool AntiAliasedLineEnds; // Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists. + bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists. + float CurveTessellationMaxError; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Colors @@ -2400,6 +2422,8 @@ struct ImGuiStyle // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + bool AntiAliasedLinesUseTex; // [OBSOLETE] Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). + float CurveTessellationTol; // [OBSOLETE] Old CurveTessellationTol = New CurveTessellationMaxError*CurveTessellationMaxError. // Changed in 1.93.0 // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9. #endif }; @@ -2535,6 +2559,12 @@ struct ImGuiIO // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. + // Option to compare < 1.93.0 rendering details vs 1.93.0 rendering details. (#9504) + // - Equivalent to calling PushDrawFlag(ImDrawFlags_StrokeLegacy,true) on every draw list instances. + // - Suggested use, e.g. io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map to SHIFT modifier + // - Also see our interactive testbed: https://www.dearimgui.com/docs/drawlist_v193 + bool ConfigDebugDrawListDefaultsToStrokeLegacy; // false // Default all ImDrawList to ImDrawFlags_StrokeLegacy mode, mimicking pre-1.93.0 rendering. + //------------------------------------------------------------------ // Platform Identifiers // (the imgui_impl_xxxx backend files are setting those up for you) @@ -2761,30 +2791,27 @@ struct ImGuiOnceUponAFrame operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } }; -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +// Helper: Parse and apply text filters e.g. 'aaa bbb' (all), 'aaa,bbb' (any), '-aaa' (exclude), '"Hello, world"' (exact sequence) +struct ImGuiTextFilterItem; struct ImGuiTextFilter { IMGUI_API ImGuiTextFilter(const char* default_filter = ""); - IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; - IMGUI_API void Build(); - void Clear() { InputBuf[0] = 0; Build(); } - bool IsActive() const { return !Filters.empty(); } + IMGUI_API void Build(); // Update internal data when filter changes + inline void Clear() { InputBuf[0] = 0; Build(); } // Clear filter + inline bool IsActive() const { return _Items.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters - // [Internal] - struct ImGuiTextRange - { - const char* b; - const char* e; + // Helper to call InputText() + Build() when buffer is changed. + IMGUI_API bool Draw(const char* label = "Filter"); + IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl -excl"); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } +#endif - ImGuiTextRange() { b = e = NULL; } - ImGuiTextRange(const char* _b, const char* _e) { b = _b; e = _e; } - bool empty() const { return b == e; } - IMGUI_API void split(char separator, ImVector* out) const; - }; - char InputBuf[256]; - ImVectorFilters; - int CountGrep; + // Members + char InputBuf[256]; // User input buffer + int _CountExclude; // [Internal] >= 0 count of leading exclude + ImVector _Items; // [Internal] Pre-parsed, trimmed, reordered items }; // Helper: Growable text buffer for logging/accumulating text @@ -2864,6 +2891,20 @@ struct ImGuiStorage #endif }; +// [Internal] Used for small stacks. Do not use. +template +struct ImSmallStack_ +{ + T LocalData[Capacity]; // FIXME: should evolve into using heap, e.g union {} with T* HeapData + add SZ_T Capacity. + SZ_T Size = 0; + + inline void clear() { Size = 0; } + inline T& back() { IM_ASSERT(Size > 0); return LocalData[Size - 1]; } + inline void push_back(const T& v) { IM_ASSERT(Size < Capacity); memcpy(&LocalData[Size], &v, sizeof(v)); Size++; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline int capacity() const { return Capacity; } +}; + // Flags for ImGuiListClipper (currently not fully exposed in function calls: a future refactor will likely add this to ImGuiListClipper::Begin function equivalent) enum ImGuiListClipperFlags_ { @@ -2937,41 +2978,41 @@ struct ImGuiListClipper #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF // ImVec2 operators -inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } -inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -inline ImVec2 operator+(const ImVec2& lhs) { return lhs; } -inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } -inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } +IM_NODEBUGSTEP inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +IM_NODEBUGSTEP inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +IM_NODEBUGSTEP inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator+(const ImVec2& lhs) { return lhs; } +IM_NODEBUGSTEP inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } +IM_NODEBUGSTEP inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +IM_NODEBUGSTEP inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +IM_NODEBUGSTEP inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } // ImVec4 operators -inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } -inline ImVec4 operator*(const float lhs, const ImVec4& rhs) { return ImVec4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } -inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } -inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } -inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } -inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } -inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } -inline ImVec4 operator+(const ImVec4& lhs) { return lhs; } -inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } -inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } -inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } -inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } -inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } -inline ImVec4& operator*=(ImVec4& lhs, const ImVec4& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z; lhs.w *= rhs.w; return lhs; } -inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } -inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } -inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } +IM_NODEBUGSTEP inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +IM_NODEBUGSTEP inline ImVec4 operator*(const float lhs, const ImVec4& rhs) { return ImVec4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } +IM_NODEBUGSTEP inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator+(const ImVec4& lhs) { return lhs; } +IM_NODEBUGSTEP inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +IM_NODEBUGSTEP inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator*=(ImVec4& lhs, const ImVec4& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z; lhs.w *= rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } +IM_NODEBUGSTEP inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } +IM_NODEBUGSTEP inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE #endif @@ -3177,15 +3218,10 @@ struct ImGuiSelectionExternalStorage }; //----------------------------------------------------------------------------- -// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. //----------------------------------------------------------------------------- -// The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. -#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) -#endif - // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). // - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. @@ -3279,40 +3315,82 @@ struct ImDrawListSplitter // Flags for ImDrawList functions enum ImDrawFlags_ { - ImDrawFlags_None = 0, + ImDrawFlags_None = 0, + //ImDrawFlags_Closed = 1, // -> FLAG MOVED BELOW. Prior to 1.92.8 (May 2026): ImDrawFlags_Closed was guaranteed to be == (1<<0) == 1, for legacy compatibility reason. Hardcoded use of 1 or true should be replaced with 'ImDrawFlags_Closed'. - // Rounding for AddRect(), AddRectFilled(), PathRect() - // - When not specified, we defaults to ImDrawFlags_RoundCornersAll! So you only need to use those flags if you want another configuration. - ImDrawFlags_RoundCornersTopLeft = 1 << 4, // Round top-left corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersTopRight = 1 << 5, // Round top-right corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // Round bottom-left corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersBottomRight = 1 << 7, // Round bottom-right corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersNone = 1 << 8, // Disable rounding even if `float rounding > 0.0f`. This is NOT zero, NOT an implicit flag! - ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!) - ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified! - ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, - ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, - ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + // About usage of flags: + // - "Prim" column: 'OK' = flag can be used to configure an individual AddXXX() call. + // - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled) + may be modified using ImDrawList::PushDrawFlag(). + // OK(0)/OK(1) indicates whether this flag is set in the default ImGui Style settings. - // Stroke options - ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. - //ImDrawFlags_Closed = 1, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. + // - Rounding default to ImDrawFlags_RoundAll when 'rounding > 0'. + // - So you only need to use the ImDrawFlags_RoundXXX flags if you want a special configuration (e.g. a rectangle with one rounded corner). + // Rounding for AddRectXXX(), PathRect() ------ // Prim/Scope? + ImDrawFlags_RoundTopLeft = 1 << 4, // OK -- // Round top-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundTopRight = 1 << 5, // OK -- // Round top-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundBottomLeft = 1 << 6, // OK -- // Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundBottomRight = 1 << 7, // OK -- // Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundNone = 1 << 8, // OK -- // Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag! + ImDrawFlags_RoundAll = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight | ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, // (Default!!) + ImDrawFlags_RoundTop = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight, + ImDrawFlags_RoundBottom = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, + ImDrawFlags_RoundLeft = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundTopLeft, + ImDrawFlags_RoundRight = ImDrawFlags_RoundBottomRight | ImDrawFlags_RoundTopRight, - ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, -}; + // Stroke Options ----------------------------- // Prim/Scope? + ImDrawFlags_Closed = 1 << 9, // OK -- // PathStroke(), AddPolyline(): specify that shape should be closed. + ImDrawFlags_JoinMiter = 1 << 10, // OK -- // PathStroke(), AddPolyline(): use miter joins/corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. + ImDrawFlags_CapSquare = 1 << 11, // OK -- // PathStroke(), AddPolyline(): use square cap line ends. -// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. -// It is however possible to temporarily alter flags between calls to ImDrawList:: functions. -enum ImDrawListFlags_ -{ - ImDrawListFlags_None = 0, - ImDrawListFlags_AntiAliasedLines = 1 << 0, // Enable anti-aliased lines/borders (*2 the number of triangles for 1.0f wide line or lines thin enough to be drawn using textures, otherwise *3 the number of triangles) - ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). - ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). - ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries. + // About Stroke Ends: + // - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It's cheaper (we can emit less vertices). + // - For more free-form drawings, light graphs and markers, or when using thick strokes: you can turn them on using the ImDrawFlags_AALineEnds flag. + // - See 'Demo->Examples->Custom Rendering' to interactively toy with those flags. + // - 'OK*' indicates using this at the AddXXX() call site is unlikely: it would only makes sense if (1) the option is disabled in style/scope and (2) you want to forcefully enable it for a single primitives. Possible but unlikely! Only supported for functions taking flags inputs. + // Stroke/Fill Anti-aliasing ------------------ // Prim/Scope? + ImDrawFlags_AAFill = 1 << 12, // OK* OK(1) // Enable anti-aliasing for Filled shapes. + ImDrawFlags_AALines = 1 << 13, // OK* OK(1) // Enable anti-aliasing for Strokes (lines, borders). + ImDrawFlags_AALineEnds = 1 << 14, // OK OK(0) // Enable anti-aliasing for Strokes Ends. Requires AALines to also be enabled. Useful on thick strokes or for precise continuity of multiple lines. A little more costly. + //ImDrawFlags_NoAA = 1 << 15, // OK -- // Disable anti-aliasing for a given primitive. + //ImDrawFlags_NoAALineEnds = 1 << 16, // OK -- // Disable anti-aliasing ends for a given primitive. + + // About Stroke Position: + // - Read https://github.com/ocornut/imgui/wiki/Draw-List (this guide includes a precise list of differences between 1.92.9 and 1.93.0) + // - Read https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering + // Stroke Position relative to shape outline -- // Prim/Scope? + ImDrawFlags_StrokeInside = 1 << 17, // OK -- // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) + ImDrawFlags_StrokeCenter = 2 << 17, // OK -- // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) + ImDrawFlags_StrokeCenterBiased = 3 << 17, // OK -- // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and the rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! + ImDrawFlags_StrokeOutside = 4 << 17, // OK -- // Draw stroke outside of the shape outline + ImDrawFlags_StrokeLegacy = 7 << 17, // OK OK(0) // Use legacy positioning + enable JoinMiter + disable AALineEnds. Must be all bits set. + + // Other Options ------------------------------ // Prim/Scope? + ImDrawFlags_TextNoPixelSnap = 1 << 20, // OK OK(0) // Disable automatically snapping AddText() calls to pixel boundaries. + ImDrawFlags_UseTexForRoundCorners = 1 << 21, // -- OK(1) // Enable using textures instead of strokes to draw rounded corners/circles where possible (faster). Used by default unless 'ImFontAtlasFlags_NoBakedRoundCorners' is enabled in the font atlas. + ImDrawFlags_UseVtxOffset = 1 << 22, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. + ImDrawFlags_AllowTexForRoundCorners_= 1 << 23, // -- OK(1) // [Internal] + + // [Internal] + ImDrawFlags_RoundMask_ = ImDrawFlags_RoundAll | ImDrawFlags_RoundNone, // [Internal] + ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundMask_, // [Internal] Values allowed in PushDrawFlag() scope. + ImDrawFlags_AllowInFrameScope_ = ImDrawFlags_AllowInPushScope_ | ImDrawFlags_AllowTexForRoundCorners_, + ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. + + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // RENAMED in 1.93.0 (Oct 2026): ImDrawFlags_RoundCornersXXXX -> ImDrawFlags_RoundXXXX. + ImDrawFlags_RoundCornersTopLeft = ImDrawFlags_RoundTopLeft, + ImDrawFlags_RoundCornersTopRight = ImDrawFlags_RoundTopRight, + ImDrawFlags_RoundCornersBottomLeft = ImDrawFlags_RoundBottomLeft, + ImDrawFlags_RoundCornersBottomRight = ImDrawFlags_RoundBottomRight, + ImDrawFlags_RoundCornersNone = ImDrawFlags_RoundNone, + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundAll, + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundTop, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundBottom, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundRight, +#endif }; // Draw command list @@ -3330,7 +3408,7 @@ struct ImDrawList ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. - ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + ImDrawFlags Flags; // Current flags for drawing primitives. Alter with PushDrawFlag()/PopDrawFlags() to adjust defaults to a given scope. // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. @@ -3343,7 +3421,10 @@ struct ImDrawList ImVector _ClipRectStack; // [Internal] ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] + ImSmallStack_ _DrawFlagsStack;// [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. + bool _FringeScaleIsInteger; // [Internal] true if 1/_FringeScale is a whole number, used to select fast path for rendering const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). @@ -3356,6 +3437,8 @@ struct ImDrawList IMGUI_API void PopClipRect(); IMGUI_API void PushTexture(ImTextureRef tex_ref); IMGUI_API void PopTexture(); + IMGUI_API void PushDrawFlag(ImDrawFlags flags, bool enabled); // [BETA] Please notify me if you are using this. + IMGUI_API void PopDrawFlag(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } @@ -3366,32 +3449,32 @@ struct ImDrawList // In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12. // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. - IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); + IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); + IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); - IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); - IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col); - IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); - IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f); + IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); - IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) - IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0, ImDrawFlags flags = 0); // Cubic Bezier (4 control points) + IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0, ImDrawFlags flags = 0); // Quadratic Bezier (3 control points) // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); - IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); + inline void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0) { _AddPolyline(points, num_points, col, thickness, flags, FLT_MAX); } + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags = 0); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives @@ -3408,9 +3491,9 @@ struct ImDrawList inline void PathClear() { _Path.Size = 0; } inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } - inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } + inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { _AddPolyline(_Path.Data, _Path.Size, col, thickness, flags, FLT_MAX); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse @@ -3477,6 +3560,7 @@ struct ImDrawList // [Internal helpers] IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); + IMGUI_API void _SetPixelDensity(float pixel_density); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); @@ -3487,6 +3571,13 @@ struct ImDrawList IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); + IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags); + IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); + IMGUI_API void _SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImVec2* out_uv1, float* out_fringe, ImDrawFlags flags); + IMGUI_API float _CalculateCenterBiasedOffset(float thickness); + IMGUI_API void _AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_offset); }; // All draw data to render a Dear ImGui frame @@ -3704,7 +3795,8 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_None = 0, ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) - ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build thick line textures into the atlas (save a little texture memory, allow support for point/nearest filtering). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). + ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build anti-aliased line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENTS ANTI-ALIASED LINES FROM WORKING AND WILL DISABLE THEM. + ImFontAtlasFlags_NoBakedRoundCorners= 1 << 3, // Don't build round corners into the atlas. }; // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: @@ -3742,8 +3834,8 @@ struct ImFontAtlas IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. - // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. - // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. + // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. + // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. // - As we are transitioning toward our new font system the semantic for those functions gets increasingly misleading and are often a source of issues. // TL;DR; most likely, don't use any of those functions. We expect to obsolete/rework them. IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! @@ -3821,7 +3913,7 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). - int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method never relies on bilinear filtering you may set this to 0. int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. @@ -3848,7 +3940,6 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. @@ -3946,7 +4037,7 @@ struct ImFont ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 17 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 8K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph (== EllipsisChar) needs to be generated by combining multiple '.'. - ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. + ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapCodepointXXX(). #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() #endif @@ -3972,8 +4063,10 @@ struct ImFont // [Internal] Don't use! IMGUI_API void ClearOutputData(); - IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. + IMGUI_API void AddRemapCodepoint(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. + IMGUI_API void AddRemapCodepointToGlyphIndex(ImWchar from_codepoint, int font_src_idx, int glyph_idx); IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); + //inline void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) { AddRemapCodepoint(from_codepoint, to_codepoint); } // Renamed in 1.93.0 (Sep 2026) }; // This is provided for consistency (but we don't actually use this) @@ -4090,6 +4183,7 @@ struct ImGuiPlatformIO ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state. ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear. ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point. + ImDrawCallback DrawCallback_SetSamplerFromTex; // Request backend to use sampler associated to texture - only available in some backends: OpenGL2/3 and SDLRenderer3. //ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data. //------------------------------------------------------------------ @@ -4224,6 +4318,18 @@ namespace ImGui #define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState +// -- RENAMED in 1.93.0: Merged ImDrawListFlags into ImDrawFlags. +typedef ImDrawFlags ImDrawListFlags; +enum ImDrawListFlags_ +{ + ImDrawListFlags_None = ImDrawFlags_None, + ImDrawListFlags_AntiAliasedFill = ImDrawFlags_AAFill, + ImDrawListFlags_AntiAliasedLines = ImDrawFlags_AALines, + ImDrawListFlags_AntiAliasedLinesUseTex = ImDrawFlags_AALines, // No effect, anti-aliased rendering always uses textures from 1.93+ + ImDrawListFlags_AllowVtxOffset = ImDrawFlags_UseVtxOffset, + ImDrawListFlags_TextNoPixelSnap = ImDrawFlags_TextNoPixelSnap, +}; + //-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect // - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y // - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h @@ -4247,16 +4353,16 @@ typedef ImFontAtlasRect ImFontAtlasCustomRect; //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ //{ -// ImDrawCornerFlags_None = ImDrawFlags_RoundCornersNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit -// ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundCornersTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). -// ImDrawCornerFlags_TopRight = ImDrawFlags_RoundCornersTopRight, // Was == 0x02 (1 << 1) prior to 1.82. -// ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundCornersBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. -// ImDrawCornerFlags_BotRight = ImDrawFlags_RoundCornersBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. -// ImDrawCornerFlags_All = ImDrawFlags_RoundCornersAll, // Was == 0x0F prior to 1.82 -// ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, -// ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, -// ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, -// ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, +// ImDrawCornerFlags_None = ImDrawFlags_RoundNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundNone which is != 0 and not implicit +// ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). +// ImDrawCornerFlags_TopRight = ImDrawFlags_RoundTopRight, // Was == 0x02 (1 << 1) prior to 1.82. +// ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. +// ImDrawCornerFlags_BotRight = ImDrawFlags_RoundBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. +// ImDrawCornerFlags_All = ImDrawFlags_RoundAll, // Was == 0x0F prior to 1.82 +// ImDrawCornerFlags_Top = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight, +// ImDrawCornerFlags_Bot = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, +// ImDrawCornerFlags_Left = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundBottomLeft, +// ImDrawCornerFlags_Right = ImDrawFlags_RoundTopRight | ImDrawFlags_RoundBottomRight, //}; // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) diff --git a/3rdparty/imgui/include/imgui_internal.h b/3rdparty/imgui/include/imgui_internal.h index d2a8595a1c..420f070e8c 100644 --- a/3rdparty/imgui/include/imgui_internal.h +++ b/3rdparty/imgui/include/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -126,7 +126,7 @@ Index of this file: // Enable stb_truetype by default unless FreeType is enabled. // You can compile with both by defining both IMGUI_ENABLE_FREETYPE and IMGUI_ENABLE_STB_TRUETYPE together. -#ifndef IMGUI_ENABLE_FREETYPE +#if !defined(IMGUI_ENABLE_FREETYPE) && !defined(IMGUI_ENABLE_STB_TRUETYPE) && !defined(IMGUI_DISABLE_STB_TRUETYPE) #define IMGUI_ENABLE_STB_TRUETYPE #endif @@ -189,6 +189,7 @@ struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aim struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) +struct ImGuiWindowStackData; // Storage for each window pushed into the stack // Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. @@ -284,7 +285,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 #define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds -#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! //#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) // Hint for branch prediction @@ -367,6 +368,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex +// - Helper: ImGuiTextFilterItem // - Helper: ImGuiStorage //----------------------------------------------------------------------------- @@ -406,7 +408,7 @@ IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF -inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } +inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c & ~32 : c; } inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } @@ -504,13 +506,23 @@ inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +// Converge to more precise solution using single step of Newton-Raphson method, repeating increases precision +inline float ImRsqrtPrecise(float x) { const float r = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); return r * (1.5f - x * 0.5f * r * r); } #else inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +inline float ImRsqrtPrecise(float x) { return 1.0f / sqrtf(x); } #endif -inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif + // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double -// (Exceptionally using templates here but we could also redefine them for those types) +// (Specialized version ensure that runtime-check pragmas/macros are applied with caller settings. Template version are less reliable in this regards) +inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } @@ -535,8 +547,9 @@ inline ImVec2 ImTrunc(const ImVec2& v) { return inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } inline float ImTrunc64(float f) { return (float)(ImS64)(f); } -inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only. -inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the the bit-hack version (search for "0x1p120f"). +inline float ImRoundPositive64(float f) { return (float)(ImS64)(f + 0.5f); } // Positive values only. +inline float ImRoundSigned64(float f) { return (float)(ImS64)(f >= 0.0f ? f + 0.5f : f - 0.5f); } +inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the bit-hack version (search for "0x1p120f"). inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } @@ -545,12 +558,29 @@ inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / (float)n; avg += sample / (float)n; return avg; } + +inline bool ImIsTruncated(float x) { return (float)(int)x == x; } +#ifdef IMGUI_ENABLE_SSE +// Returns true if all parameters are whole numbers. +inline bool ImIsTruncated4(float x, float y, float z, float w) +{ + __m128 v = _mm_set_ps(x, y, z, w); + __m128i i = _mm_cvttps_epi32(v); + return _mm_movemask_ps(_mm_cmpeq_ps(v, _mm_cvtepi32_ps(i))) == 0xF; +} +#else +inline bool ImIsTruncated4(float x, float y, float z, float w) +{ + return (float)(int)x == x && (float)(int)y == y && (float)(int)z == z && (float)(int)w == w; +} +#endif + IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry IMGUI_API ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t); -IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments -IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol);// For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol +IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments +IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float max_error);// For auto-tessellated curves you can use max_error = style.CurveTessellationMaxError IMGUI_API ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t); IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); @@ -565,25 +595,25 @@ IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec1 { float x; - constexpr ImVec1() : x(0.0f) { } - constexpr ImVec1(float _x) : x(_x) { } + IM_NODEBUGSTEP constexpr inline ImVec1() : x(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec1(float _x) : x(_x) { } }; // Helper: ImVec2i (2D vector, integer) struct ImVec2i { int x, y; - constexpr ImVec2i() : x(0), y(0) {} - constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} + IM_NODEBUGSTEP constexpr inline ImVec2i() : x(0), y(0) {} + IM_NODEBUGSTEP constexpr inline ImVec2i(int _x, int _y) : x(_x), y(_y) {} }; // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { short x, y; - constexpr ImVec2ih() : x(0), y(0) {} - constexpr ImVec2ih(short _x, short _y) : x(_x), y(_y) {} - constexpr explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {} + IM_NODEBUGSTEP constexpr inline ImVec2ih() : x(0), y(0) {} + IM_NODEBUGSTEP constexpr inline ImVec2ih(short _x, short _y) : x(_x), y(_y) {} + IM_NODEBUGSTEP constexpr inline explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {} }; // Helper: ImRect (2D axis aligned bounding-box) @@ -593,10 +623,10 @@ struct IMGUI_API ImRect ImVec2 Min; // Upper-left ImVec2 Max; // Lower-right - constexpr ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} - constexpr ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} - constexpr ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} - constexpr ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + IM_NODEBUGSTEP constexpr inline ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} + IM_NODEBUGSTEP constexpr inline ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + IM_NODEBUGSTEP constexpr inline ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + IM_NODEBUGSTEP constexpr inline ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } @@ -698,13 +728,13 @@ struct ImSpan inline bool empty() const { return Data == DataEnd; } inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } - inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } - inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } - inline T* begin() { return Data; } - inline const T* begin() const { return Data; } - inline T* end() { return DataEnd; } - inline const T* end() const { return DataEnd; } + IM_NODEBUGSTEP inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + IM_NODEBUGSTEP inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + IM_NODEBUGSTEP inline T* begin() { return Data; } + IM_NODEBUGSTEP inline const T* begin() const { return Data; } + IM_NODEBUGSTEP inline T* end() { return DataEnd; } + IM_NODEBUGSTEP inline const T* end() const { return DataEnd; } // Utilities inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } @@ -835,6 +865,16 @@ struct ImGuiTextIndex void append(const char* base, int old_size, int new_size); }; +// [Internal] Types +struct ImGuiTextFilterItem +{ + const char* Begin; // Pointer within parent's buffer + int Len; // Length of text (non-zero terminated) + short CountInclude; // Number of consecutive items forming an end chain. >0 when beginning of an AND chain, =0 otherwise. + unsigned short MatchIncludesMask; // Storage for multi-sources versions of PassFilter(). Unused by core library. + ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; MatchIncludesMask = 0; } +}; + // Helper: ImGuiPackedDate (sizeof() == 2) // Store a date in a way that is efficient to read/write in text form. If we stored e.g. number of days since Epoch we'd need costlier back and forth. // This is specifically designed to be able to prune old .ini data. @@ -877,11 +917,28 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI)))) #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD)) -// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle. +// ImDrawList: Lookup tables #ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE -#define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table. +#define IM_DRAWLIST_ARCFAST_TABLE_SIZE (48) // Number of samples in adaptive arc drawing lookup table. #endif -#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. +#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. + +#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. +#endif +#ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] +#endif +#ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT +#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. +#endif +#ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX +#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) +#endif +#ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX +#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness +#endif +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT ((IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH - 1) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 1) // Data shared between all ImDrawList instances // Conceptually this could have been called e.g. ImDrawListSharedContext @@ -890,15 +947,15 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->Builder->TexUvLines) + const ImVec4* TexUvCorners; // UV of rounded corner (== FontAtlas->Builder->TexUvCorners) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) - float FontSize; // Current font size (used for for simplified AddText overload) + float FontSize; // Current font size (used for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) - float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() + float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc - float InitialFringeScale; // Initial scale to apply to AA fringe - ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + ImDrawFlags InitialDrawFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData @@ -933,6 +990,8 @@ struct ImFontStackData // [SECTION] Style support //----------------------------------------------------------------------------- +#define ImGuiCol_TextMixedValue ImGuiCol_TextDisabled + struct ImGuiStyleVarInfo { ImU32 Count : 8; // 1+ @@ -994,7 +1053,6 @@ enum ImGuiItemFlagsPrivate_ { // Controlled by user ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). @@ -1030,7 +1088,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited. - + // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) @@ -1084,12 +1142,6 @@ enum ImGuiButtonFlagsPrivate_ //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; -// Extend ImGuiComboFlags_ -enum ImGuiComboFlagsPrivate_ -{ - ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() -}; - // Extend ImGuiSliderFlags_ enum ImGuiSliderFlagsPrivate_ { @@ -1184,6 +1236,10 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +//ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() // [Obsoleted in 1.93.0] Unnecessary: can now use BeginComboPreview() without a flag. +#endif + // Storage data for BeginComboPreview()/EndComboPreview() struct IMGUI_API ImGuiComboPreviewData { @@ -1192,7 +1248,10 @@ struct IMGUI_API ImGuiComboPreviewData ImVec2 BackupCursorMaxPos; ImVec2 BackupCursorPosPrevLine; float BackupPrevLineTextBaseOffset; - ImGuiLayoutType BackupLayout; + float BackupWorkRectMaxX; + float BackupContentRectMaxX; + ImGuiLayoutType BackupLayout : 8; + int WithinPreview : 2; ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } }; @@ -1281,6 +1340,7 @@ struct IMGUI_API ImGuiInputTextState bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool EditedBefore; // edited since activated bool EditedThisFrame; // edited this frame + bool ValidatedThisFrame; bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. ImS8 LastMoveDirectionLR; // ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s int ReloadSelectionStart; @@ -1361,8 +1421,10 @@ struct ImGuiNextWindowData ImVec2 SizeVal; ImVec2 ContentSizeVal; ImVec2 ScrollVal; - ImGuiWindowFlags WindowFlags; // Only honored by BeginTable() - ImGuiChildFlags ChildFlags; + ImGuiWindowFlags WindowFlagsSet; // Flags to set. Use SetNextWindowFlags()! + ImGuiWindowFlags WindowFlagsClear; + ImGuiChildFlags ChildFlagsSet; // Flags to set. Use SetNextWindowChildFlags(). + ImGuiChildFlags ChildFlagsClear; bool CollapsedVal; ImRect SizeConstraintRect; ImGuiSizeCallback SizeCallback; @@ -1456,7 +1518,7 @@ struct IMGUI_API ImGuiErrorRecoveryState ImGuiErrorRecoveryState() { memset((void*)this, 0, sizeof(*this)); } }; -// Data saved for each window pushed into the stack +// Storage for each window pushed into the stack. struct ImGuiWindowStackData { ImGuiWindow* Window; @@ -1464,6 +1526,7 @@ struct ImGuiWindowStackData ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting bool DisabledOverrideReenable; // Non-child window override disabled flag float DisabledOverrideReenableAlphaBackup; + ImRect ParentLastComboPreviewRect; }; struct ImGuiShrinkWidthItem @@ -1998,8 +2061,9 @@ struct IMGUI_API ImGuiMultiSelectState // Every instance of ImGuiViewport is in fact a ImGuiViewportP. struct ImGuiViewportP : public ImGuiViewport { - float BgFgDrawListsLastTimeActive[2]; // Last frame number the background (0) and foreground (1) draw lists were used ImDrawList* BgFgDrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays. + int BgFgDrawListsLastFrameActive[2]; // Last frame the background (0) and foreground (1) draw lists were used. + float BgFgDrawListsLastTimeActive[2]; // Timestamps for Gc. ImDrawData DrawDataP; ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData @@ -2012,7 +2076,7 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset ImVec2 BuildWorkInsetMax; // " - ImGuiViewportP() { BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0f; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; } + ImGuiViewportP() { BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; BgFgDrawListsLastFrameActive[0] = BgFgDrawListsLastFrameActive[1] = -1; BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0; } ~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); } // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) @@ -2267,7 +2331,7 @@ struct ImGuiContext float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified. float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. - float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). + float CurrentPixelDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; ImGuiID WithinEndChildID; // Set within EndChild() @@ -2303,6 +2367,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information + const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. Set to NULL to display original value. ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame @@ -2437,6 +2502,7 @@ struct ImGuiContext ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyChord NavJustMovedToKeyMods; bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. + bool NavJustMovedToIsInit; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. // Navigation: extra config options (will be made public eventually) @@ -2690,7 +2756,7 @@ struct IMGUI_API ImGuiWindowTempData ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. Sync any changes with TREE_NODE_MAX_DEPTH/TREE_NODE_GET_DEPTH_MASK! ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) @@ -3117,9 +3183,9 @@ struct IMGUI_API ImGuiTable ImDrawListSplitter* DrawSplitter; // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly ImGuiTableInstanceData InstanceDataFirst; ImVector InstanceDataExtra; // FIXME-OPT: Using a small-vector pattern would be good. - ImGuiTableColumnSortSpecs SortSpecsSingle; - ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. + ImGuiTableColumnSortSpecs SortSpecsSingle; ImGuiTableColumnIdx SortSpecsCount; ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns using fixed width (<= ColumnsCount) @@ -3151,6 +3217,7 @@ struct IMGUI_API ImGuiTable ImS8 NavLayer; // ImGuiNavLayer at the time of BeginTable(). bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsNewTable; bool IsInitializing; bool IsReconcileMode; bool IsSortSpecsDirty; @@ -3252,7 +3319,7 @@ struct ImGuiTableSettings }; namespace ImGui -{ +{ // Tables: Candidates for public API IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableSetColumnWidth(int column_n, float width); @@ -3346,7 +3413,7 @@ namespace ImGui // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. IMGUI_API ImGuiIO& GetIO(ImGuiContext* ctx); IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx); - inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: I don't want to formalize this just yet. Because reasons. Please don't use. + inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: PLEASE DO NOT USE. I don't want to formalize this just yet. Because reasons. inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -3369,6 +3436,8 @@ namespace ImGui inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } inline ImVec2 WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } + IMGUI_API void SetNextWindowFlags(ImGuiWindowFlags flags, bool enabled); + IMGUI_API void SetNextWindowChildFlags(ImGuiChildFlags flags, bool enabled); // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0); @@ -3390,8 +3459,8 @@ namespace ImGui IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); - IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); - inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } + IMGUI_API void SetPixelDensity(float pixel_density); // was SetFontRasterizerDensity() + inline float GetPixelDensity() { return GImGui->CurrentPixelDensity; } inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } IMGUI_API ImFont* GetDefaultFont(); IMGUI_API void PushPasswordFont(); @@ -3531,7 +3600,7 @@ namespace ImGui // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); - IMGUI_API bool BeginComboPreview(); + IMGUI_API bool BeginComboPreview(); // Submit preview contents a combo. Call this after EndCombo() to display contents that's more than just a text label. IMGUI_API void EndComboPreview(); // Keyboard/Gamepad Navigation @@ -3664,6 +3733,7 @@ namespace ImGui IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb); + IMGUI_API void RenderDragDropTargetRectForViewport(ImGuiID viewport_id, const ImRect& bb); IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding); // Typing-Select API @@ -3696,6 +3766,7 @@ namespace ImGui IMGUI_API ImGuiTabItem* TabBarGetCurrentTab(ImGuiTabBar* tab_bar); inline int TabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { return tab_bar->Tabs.index_from_ptr(tab); } IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API ImVec2 TabBarGetTabPos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); @@ -3721,7 +3792,7 @@ namespace ImGui IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorComponentMarker(const ImRect& bb, ImU32 col, float rounding); - IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float alpha, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None, float rounding = -1.0f); // Navigation highlight #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4 @@ -3740,8 +3811,8 @@ namespace ImGui // Widgets: Text IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); - IMGUI_API void TextAligned(float align_x, float size_x, const char* fmt, ...); // FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) - IMGUI_API void TextAlignedV(float align_x, float size_x, const char* fmt, va_list args); + IMGUI_API void TextAligned(float align_x, float width, const char* fmt, ...) IM_FMTARGS(3); // FIXME-WIP: Works but API is likely to be reworked. + IMGUI_API void TextAlignedV(float align_x, float width, const char* fmt, va_list args) IM_FMTLIST(3); // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); @@ -3906,10 +3977,11 @@ struct ImFontLoader void (*LoaderShutdown)(ImFontAtlas* atlas); bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); - bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + int (*FontSrcGetGlyphIndexFromCodepoint)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); // Required + bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, int glyph_index); bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); - bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x); + bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, int glyph_index, ImFontGlyph* out_glyph, float* out_advance_x); // Required // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. // FIXME: At this point the two other types of buffers may be managed by core to be consistent? @@ -4011,8 +4083,14 @@ struct ImFontAtlasBuilder // Custom rectangle identifiers ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; + ImFontAtlasRectId PackIdLineFractTexData; + ImFontAtlasRectId PackIdCornersTexData; - ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } + // Cached UV coordinates + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]; // UVs for baked anti-aliased lines (u0, u1, v, 1/thickness) + ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) + + ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdLineFractTexData = PackIdCornersTexData = -1; } }; IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); diff --git a/3rdparty/imgui/src/imgui.cpp b/3rdparty/imgui/src/imgui.cpp index 7fa2d49e8d..3705e7262e 100644 --- a/3rdparty/imgui/src/imgui.cpp +++ b/3rdparty/imgui/src/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (main code and documentation) // Help: @@ -230,7 +230,7 @@ CODE This is to increase compatibility, increase maintainability and facilitate use from other languages. - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types. See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that. - We can can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally. + We can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally. - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (so don't use ImVector in your code or at our own risk!). - Building: We don't use nor mandate a build system for the main library. @@ -395,6 +395,51 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + 2026/10/07 (1.93.0) - large cleanup/refactor of ImDrawList. many issues and inconsistencies fixed! (#9504) + BELOW IS A SIMPLIFIED/SHORTENED RECAP OF CHANGES. READ CHANGELOG AND LINKS ABOVE FOR MORE DETAILS! + useful links: + - ImDrawList 1.92.9 <> 1.93 Interactive Testbed: https://www.dearimgui.com/docs/drawlist_v193 (<-- you can download this locally) + - ImDrawList Wiki Reference: https://github.com/ocornut/imgui/wiki/Draw-List + - Discussion thread: https://github.com/ocornut/imgui/issues/9504 + - use ImDrawFlags_StrokeLegacy mode to emulate old rendering: + - Per-primitive: draw_list->AddLine(..., ..., ImDrawFlags_StrokeLegacy); + - For a given scope: draw_list->PushDrawFlag(ImDrawFlags_StrokeLegacy, true); draw_list->AddLine(...); draw_list->PopDrawFlag(); + - Globally: ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); // Map to a checkbox + - Globally: io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on Shift + - AddLine: removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates by this function, and led to lots of inconsistencies. + - This fixes inconsistencies in the API and matches the PathXXX API. + - By default, stroke thickness extends on both side of the given segment. e.g for a "pixel-perfect" looking line with thickness=1.0f, coords should be passed as center of each ends of the line. + - Use `ImDrawFlags_StrokeLegacy` to use old offset if required. Or you can apply the offset manually! + - Generally better, simpler and faster to use `AddLineH()`, `AddLineV()` functions added in 1.92.8. + - IF YOU ARE MINDFUL OF PIXEL-PERFECTNESS IN YOUR CUSTOM RENDERING/WIDGETS read Changelog and links above for details. + - AddRect(), AddCircle(), AddNgon(), AddEllipse(), AddTriangle(), AddQuad(): defaulting to "inside" stroke. All closed shapes with thickness=1.0f will appear identical. The difference for thickness>1.0f shapes may be minimal since very large strokes were not well supported for widgets, but stroke will default inside widgets. + - AddCircle(), AddNgon(): removed +0.5f offset added to radius. This fixes inconsistencies in the API. Use `ImDrawFlags_StrokeLegacy` to use old method if required. Or you can apply the offset manually! + - AddRectFilled(): non-integer coordinates will now display anti-aliased edges. Previously, non-integer coordinates rendered with aliased edges snapped by the rasterizer. + - AddRect(), AddRectFilled(): rectangles with inverted coordinates won't be visible unless using ImDrawFlags_StrokeLegacy mode. + With inverted coordinates: + - Legacy `AddRect()` rounding off -> visible but incorrect outer size. + - Legacy `AddRect()` rounding on -> visible but very glitchy. + - Legacy `AddRectFilled()` rounding off -> visible and correct. + - Legacy `AddRectFilled()` rounding on -> visible but very glitchy. + - merged ImDrawListFlags into ImDrawFlags. obsoleted ImDrawListFlags (which were rarely used directly): + - ImDrawListFlags_AntiAliasedLines -> ImDrawFlags_AALines, + - ImDrawListFlags_AntiAliasedFill -> ImDrawFlags_AAFill, + - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, + - ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap, + unifying them allows easily using them for both per-primitives alterations and scope alterations. + - renamed ImDrawFlags_RoundCornersXXXX to ImDrawFlags_RoundXXXX. + - ImDrawFlags_RoundCornersAll -> ImDrawFlags_RoundAll, + - ImDrawFlags_RoundCornersNone -> ImDrawFlags_RoundNone, + - ImDrawFlags_RoundCornersTopLeft -> ImDrawFlags_RoundTopLeft, + - ImDrawFlags_RoundCornersTopRight -> ImDrawFlags_RoundTopRight, + - ImDrawFlags_RoundCornersTop -> ImDrawFlags_RoundTop, + - ImDrawFlags_RoundCornersBottom -> ImDrawFlags_RoundBottom, + etc. kept redirection enums (will obsolete). + - obsoleted style.AntiAliasedLinesUseTex and ImDrawListFlags_AntiAliasedLinesUseTex, as the new line rendering code always uses textures. + - 2026/09/30 (1.93.0) - ImFont: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). (#609, #5748) + - 2026/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. + - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol` (default 1.25) which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12) which is in Pixels unit. + - style.CurveTessellationMaxError == sqrtf(style.CurveTessellationTol). - 2026/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: inputting a value with the keyboard doesn't write intermediate values to backing variable. (#9476) - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. @@ -1385,10 +1430,9 @@ static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSetti static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); // Platform Dependents default implementation for ImGuiPlatformIO functions +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io); static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx); static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text); -static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); -static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); namespace ImGui { @@ -1570,10 +1614,12 @@ ImGuiStyle::ImGuiStyle() DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). - AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). - CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + // Rendering & Tessellation + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. + AntiAliasedLineEnds = false; // Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists. + AntiAliasedFill = true; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists. + CurveTessellationMaxError = 1.12f; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Behaviors @@ -1586,6 +1632,10 @@ ImGuiStyle::ImGuiStyle() // [Internal] _MainScale = 1.0f; _NextFrameFontSizeBase = 0.0f; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + CurveTessellationTol = 0.0f; // Old CurveTessellationTol = CurveTessellationMaxError*CurveTessellationMaxError. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). +#endif // Default theme ImGui::StyleColorsDark(this); @@ -2100,7 +2150,7 @@ ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec } // Closely mimics PathBezierToCasteljau() in imgui_draw.cpp -static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float max_error_sqr, int level) { float dx = x4 - x1; float dy = y4 - y1; @@ -2108,7 +2158,7 @@ static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_cl float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; - if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + if ((d2 + d3) * (d2 + d3) < max_error_sqr * (dx * dx + dy * dy)) { ImVec2 p_current(x4, y4); ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); @@ -2128,20 +2178,21 @@ static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_cl float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, max_error_sqr, level + 1); + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, max_error_sqr, level + 1); } } -// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol +// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationMaxError // Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically. -ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol) +ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float max_error) { - IM_ASSERT(tess_tol > 0.0f); + IM_ASSERT(max_error > 0.0f); ImVec2 p_last = p1; ImVec2 p_closest; float p_closest_dist2 = FLT_MAX; - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); + float max_error_sqr = max_error * max_error; + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, max_error_sqr, 0); return p_closest; } @@ -2485,7 +2536,13 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) #else while (data + 4 <= data_end) { +#if defined(_DEBUG) || defined(_MSC_VER) crc = _mm_crc32_u32(crc, *(ImU32*)data); +#else + ImU32 v; + memcpy(&v, data, sizeof(ImU32)); // Avoid aliasing violation (#9557) + crc = _mm_crc32_u32(crc, v); +#endif data += 4; } while (data < data_end) @@ -3082,11 +3139,11 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // [SECTION] ImGuiTextFilter //----------------------------------------------------------------------------- -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +// Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'. ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; - CountGrep = 0; + _CountExclude = 0; if (default_filter) { ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); @@ -3094,84 +3151,110 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 } } -bool ImGuiTextFilter::Draw(const char* label, float width) +bool ImGuiTextFilter::Draw(const char* label) { - if (width != 0.0f) - ImGui::SetNextItemWidth(width); - bool value_changed = ImGui::InputText(label, InputBuf, IM_COUNTOF(InputBuf)); + return DrawWithHint(label, "incl -excl"); +} + +// Use ImGui::SetNextItemWidth() manually if you want to use this. +bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) +{ + bool value_changed = ImGui::InputTextWithHint(label, hint, InputBuf, IM_COUNTOF(InputBuf)); if (value_changed) Build(); return value_changed; } -void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector* out) const -{ - out->resize(0); - const char* wb = b; - const char* we = wb; - while (we < e) - { - if (*we == separator) - { - out->push_back(ImGuiTextRange(wb, we)); - wb = we + 1; - } - we++; - } - if (wb != we) - out->push_back(ImGuiTextRange(wb, we)); -} - +// Parse filter and split into a format optimal for PassFilter() void ImGuiTextFilter::Build() { - Filters.resize(0); - ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); - input_range.split(',', &Filters); + _Items.resize(0); + _CountExclude = 0; - CountGrep = 0; - for (ImGuiTextRange& f : Filters) + const char* buf_e = InputBuf + ImStrlen(InputBuf); + const char* word_e; + int seq_start_idx = -1; + for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) { - while (f.b < f.e && ImCharIsBlankA(f.b[0])) - f.b++; - while (f.e > f.b && ImCharIsBlankA(f.e[-1])) - f.e--; - if (f.empty()) - continue; - if (f.b[0] != '-') - CountGrep += 1; - } -} - -bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const -{ - if (Filters.Size == 0) - return true; - - if (text == NULL) - text = text_end = ""; - - for (const ImGuiTextRange& f : Filters) - { - if (f.b == f.e) - continue; - if (f.b[0] == '-') + // Trim blanks + while (word_b < buf_e && ImCharIsBlankA(word_b[0])) // FIXME: UTF-8 support + word_b++; + const bool is_excl = (word_b < buf_e && word_b[0] == '-'); + if (is_excl) + word_b++; + const bool is_quote = (word_b < buf_e && word_b[0] == '\"'); + if (is_quote) { - // Subtract - if (ImStristr(text, text_end, f.b + 1, f.e) != NULL) - return false; + // Parsing quotes. Omit storing leading/trailing quotes. + word_e = ImStrchrRange(++word_b, buf_e, '\"'); + if (word_e == NULL) + word_e = buf_e; } else { - // Grep - if (ImStristr(text, text_end, f.b, f.e) != NULL) - return true; + // Handle both ' ' and ',' separators. + for (word_e = word_b; word_e < buf_e; word_e++) + if (*word_e == ' ' || *word_e == ',') + break; } + + // Min length + if (word_e - word_b > 0) + { + // Add to list. The '-' is not stored in items but implicitly inferred using (n < CountExclude). + // FIXME-OPT: as items are derived from user input buffer and we expect the insert() to behave sanely. + if (is_excl) + { + _Items.insert(_Items.Data + _CountExclude, ImGuiTextFilterItem(word_b, word_e)); + _CountExclude++; + if (seq_start_idx != -1) + seq_start_idx++; + } + else + { + if (seq_start_idx == -1) + seq_start_idx = _Items.Size; + _Items.insert(_Items.Data + _Items.Size, ImGuiTextFilterItem(word_b, word_e)); + _Items.Data[seq_start_idx].CountInclude++; + } + } + + // Next sequence + if (word_e[0] == ',') + seq_start_idx = -1; } +} - // Implicit * grep - if (CountGrep == 0) +// FIXME: Could use a specialized ImStristr() + pre-convert our filter to uppercase. +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (_Items.Size == 0) return true; + if (text == NULL) + text = text_end = ""; + // Filters are sorted so that '-' ones are always leading. + ImGuiTextFilterItem* seq = _Items.Data; + ImGuiTextFilterItem* seq_excl_end = _Items.Data + _CountExclude; + for (; seq < seq_excl_end; seq++) + if (ImStristr(text, text_end, seq->Begin, seq->Begin + seq->Len) != NULL) + return false; + + // Process includes + ImGuiTextFilterItem* seq_end = _Items.Data + _Items.Size; + if (seq == seq_end) // When no inclusion are specified (only exclusions) we implicitly pass + return true; + while (seq < seq_end) + { + ImGuiTextFilterItem* seq_incl_end = seq + seq->CountInclude; + IM_ASSERT_PARANOID(seq->CountInclude > 0 && seq_incl_end <= seq_end); + for (; seq < seq_incl_end; seq++) + if (ImStristr(text, text_end, seq->Begin, seq->Begin + seq->Len) == NULL) + break; + if (seq == seq_incl_end) // All matched + return true; + seq = seq_incl_end; // Try next + } return false; } @@ -3938,6 +4021,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end // Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take // better advantage of the render function taking size into account for coarse clipping. void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +// DOES NOT CALL LogRenderedText(), unlike RenderTextClipped!!! { // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; @@ -4232,7 +4316,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Font = NULL; FontBaked = NULL; FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; - FontRasterizerDensity = 1.0f; + CurrentPixelDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) IO.Fonts->OwnerContext = this; @@ -4329,7 +4413,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0; NavJustMovedToKeyMods = ImGuiMod_None; - NavJustMovedToIsTabbing = false; + NavJustMovedToIsTabbing = NavJustMovedToIsInit = false; NavJustMovedToHasSelectionData = false; // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... @@ -4373,6 +4457,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + MixedValueLabel = "-"; InputTextPasswordFontBackupFlags = ImFontFlags_None; InputTextReactivateId = 0; TempInputId = 0; @@ -4478,10 +4563,7 @@ void ImGui::Initialize() LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_COUNTOF(GLocalizationEntriesEnUS)); // Setup default ImGuiPlatformIO clipboard/IME handlers. - g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations - g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; - g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; - g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; + InitializeDefaultPlatformHandlers(g.PlatformIO); // Setup session starting date #ifndef IMGUI_DISABLE_TIME_FUNCTIONS @@ -4703,7 +4785,7 @@ static void SetCurrentWindow(ImGuiWindow* window) if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) { ImGuiViewport* viewport = window->Viewport; - g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + g.CurrentPixelDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetPixelDensity() } const bool backup_skip_items = window->SkipItems; window->SkipItems = false; @@ -5297,11 +5379,13 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw } // Our ImDrawList system requires that there is always a command - if (viewport->BgFgDrawListsLastTimeActive[drawlist_no] != (float)g.Time) + if (viewport->BgFgDrawListsLastFrameActive[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); + draw_list->_SetPixelDensity(viewport->FramebufferScale.x); draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); + viewport->BgFgDrawListsLastFrameActive[drawlist_no] = g.FrameCount; viewport->BgFgDrawListsLastTimeActive[drawlist_no] = (float)g.Time; } return draw_list; @@ -5572,18 +5656,23 @@ static void SetupDrawListSharedData() for (ImGuiViewportP* viewport : g.Viewports) virtual_space.Add(viewport->GetMainRect()); g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); - g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.CurveTessellationMaxError = g.Style.CurveTessellationMaxError; g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); - g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; - if (g.Style.AntiAliasedLines) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; + g.DrawListSharedData.InitialDrawFlags = ImDrawFlags_None; + if (g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines) + g.Style.AntiAliasedLines = g.Style.AntiAliasedLineEnds = false; if (g.Style.AntiAliasedFill) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AAFill; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALines; + if (g.Style.AntiAliasedLineEnds) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALineEnds; + if (g.IO.ConfigDebugDrawListDefaultsToStrokeLegacy) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_StrokeLegacy; + if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; - g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments. + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseVtxOffset; } void ImGui::NewFrame() @@ -6204,7 +6293,7 @@ void ImGui::Render() for (ImGuiViewportP* viewport : g.Viewports) { InitViewportDrawData(viewport); - if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastTimeActive[0] == (float)g.Time) + if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastFrameActive[0] == g.FrameCount) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); } @@ -6236,7 +6325,7 @@ void ImGui::Render() FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder); // Add foreground ImDrawList (for each active viewport) - if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastTimeActive[1] == (float)g.Time) + if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastFrameActive[1] == g.FrameCount) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport)); // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch). @@ -6529,6 +6618,9 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I ImGuiWindow* parent_window = g.CurrentWindow; IM_ASSERT(id != 0); + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) + child_flags = (child_flags | g.NextWindowData.ChildFlagsSet) & ~g.NextWindowData.ChildFlagsClear; + // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; IM_UNUSED(ImGuiChildFlags_SupportedMask_); @@ -6591,13 +6683,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } } SetNextWindowSize(size); - - // Forward child flags (we allow prior settings to merge but it'll only work for adding flags) - if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) - g.NextWindowData.ChildFlags |= child_flags; - else - g.NextWindowData.ChildFlags = child_flags; - g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; + SetNextWindowChildFlags(child_flags, true); // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. @@ -7267,7 +7353,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { float y = window->Pos.y + window->TitleBarHeight - 1; - window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); + window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } } @@ -7315,7 +7401,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (bg_col & IM_COL32_A_MASK) { ImRect bg_rect(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size); - ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersBottom; + ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundBottom; ImDrawList* bg_draw_list = window->DrawList; bg_draw_list->AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags); } @@ -7325,7 +7411,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) { ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundTop); } // Menu bar @@ -7333,9 +7419,9 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } // Scrollbars @@ -7542,6 +7628,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (g.DebugBreakInWindow == window->ID) IM_DEBUG_BREAK(); + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) + flags = (flags | g.NextWindowData.WindowFlagsSet) & ~g.NextWindowData.WindowFlagsClear; + // Automatically disable manual moving/resizing when NoInputs is set if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; @@ -7567,7 +7656,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { UpdateWindowInFocusOrderList(window, window_just_created, flags); window->Flags = (ImGuiWindowFlags)flags; - window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0; + window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlagsSet : 0; window->LastFrameActive = current_frame; window->LastTimeActive = (float)g.Time; window->BeginOrderWithinParent = 0; @@ -7721,6 +7810,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) size_t buf_len = (size_t)window->NameBufLen; window->Name = ImStrdupcpy(window->Name, &buf_len, name); window->NameBufLen = (int)buf_len; + window->DrawList->_OwnerName = window->Name; } // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS @@ -8073,6 +8163,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); + window->DrawList->_SetPixelDensity(window->Viewport->FramebufferScale.x); window->DrawList->PushTexture(g.Font->OwnerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); @@ -8219,7 +8310,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Set default BgClickFlags // This is set at the end of this function, so UpdateWindowManualResize()/ClampWindowPos() may use last-frame value if overridden by user code. // FIXME: The general intent is that we will later expose config options to default to enable scrolling + select scrolling mouse button. - window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); + window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); // -V1004 // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. @@ -8825,6 +8916,26 @@ void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) g.NextWindowData.RefreshFlagsVal = flags; } +void ImGui::SetNextWindowFlags(ImGuiWindowFlags flags, bool enabled) +{ + ImGuiContext& g = *GImGui; + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) == 0) + g.NextWindowData.WindowFlagsSet = g.NextWindowData.WindowFlagsClear = ImGuiChildFlags_None; + g.NextWindowData.WindowFlagsSet = enabled ? (g.NextWindowData.WindowFlagsSet | flags) : (g.NextWindowData.WindowFlagsSet & ~flags); + g.NextWindowData.WindowFlagsClear = !enabled ? (g.NextWindowData.WindowFlagsClear | flags) : (g.NextWindowData.WindowFlagsClear & ~flags); + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasWindowFlags; +} + +void ImGui::SetNextWindowChildFlags(ImGuiChildFlags flags, bool enabled) +{ + ImGuiContext& g = *GImGui; + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) == 0) + g.NextWindowData.ChildFlagsSet = g.NextWindowData.ChildFlagsClear = ImGuiChildFlags_None; + g.NextWindowData.ChildFlagsSet = enabled ? (g.NextWindowData.ChildFlagsSet | flags) : (g.NextWindowData.ChildFlagsSet & ~flags); + g.NextWindowData.ChildFlagsClear = !enabled ? (g.NextWindowData.ChildFlagsClear | flags) : (g.NextWindowData.ChildFlagsClear & ~flags); + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; +} + ImDrawList* ImGui::GetWindowDrawList() { ImGuiWindow* window = GetCurrentWindow(); @@ -9041,7 +9152,7 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) // - UnregisterFontAtlas() [Internal] // - SetCurrentFont() [Internal] // - UpdateCurrentFontSize() [Internal] -// - SetFontRasterizerDensity() [Internal] +// - SetPixelDensity() [Internal] // - PushFont() // - PopFont() //----------------------------------------------------------------------------- @@ -9234,7 +9345,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) final_size = GetRoundedFontSize(final_size); final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) - g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.Font->CurrentRasterizerDensity = g.CurrentPixelDensity; g.FontSize = final_size; g.DrawListSharedData.FontSize = g.FontSize; @@ -9258,13 +9369,13 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) // Exposed in case user may want to override setting density. // IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. -void ImGui::SetFontRasterizerDensity(float rasterizer_density) +void ImGui::SetPixelDensity(float pixel_density) { ImGuiContext& g = *GImGui; IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); - if (g.FontRasterizerDensity == rasterizer_density) + if (g.CurrentPixelDensity == pixel_density) return; - g.FontRasterizerDensity = rasterizer_density; + g.CurrentPixelDensity = pixel_density; UpdateCurrentFontSize(0.0f); } @@ -10214,12 +10325,11 @@ void ImGui::TeleportMousePos(const ImVec2& pos) //IMGUI_DEBUG_LOG_IO("TeleportMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); } -// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() { ImGuiContext& g = *GImGui; if (g.BeginPopupStack.Size > 0) - return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; + return g.BeginPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; return g.IO.MousePos; } @@ -11098,7 +11208,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.CurveTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting!"); @@ -11138,6 +11248,11 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); }; if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl)) g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text); }; + + // Remap legacy CurveTessellationTol into CurveTessellationMaxError (OBSOLETED in 1.93.0, August 2026) + if (g.Style.CurveTessellationTol != 0.0f) + g.Style.CurveTessellationMaxError = ImSqrt(g.Style.CurveTessellationTol); + g.Style.CurveTessellationTol = 0.0f; #endif } @@ -12020,7 +12135,7 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) } scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0f)); + scroll[axis] = ImRoundPositive64(ImMax(scroll[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } @@ -12660,13 +12775,7 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext // As we bypass BeginChild(), set ImGuiChildFlags_AlwaysAutoResize as it is checked independently from ImGuiWindowFlags_AlwaysAutoResize for now (see #9355) // Ideally we should remove setting ImGuiWindowFlags_AlwaysAutoResize in BeginChild(). if ((extra_window_flags & ImGuiWindowFlags_ChildWindow) && (extra_window_flags & ImGuiWindowFlags_AlwaysAutoResize)) - { - if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) - g.NextWindowData.ChildFlags |= ImGuiChildFlags_AlwaysAutoResize; - else - g.NextWindowData.ChildFlags = ImGuiChildFlags_AlwaysAutoResize; - g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; - } + SetNextWindowChildFlags(ImGuiChildFlags_AlwaysAutoResize, true); char name[128]; IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); @@ -12767,7 +12876,7 @@ bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, id) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) return true; if (g.NavOpenContextMenuItemId == id && (IsItemFocused() || id == g.CurrentWindow->MoveId)) return true; @@ -12778,7 +12887,7 @@ bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, ImGuiKeyOwner_NoOwner) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) return true; if (g.NavOpenContextMenuWindowId && g.CurrentWindow->ID) @@ -12805,7 +12914,7 @@ bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags // This is a helper to handle the simplest case of associating one named popup to one given widget. // - To create a popup associated to the last item, you generally want to pass a NULL value to str_id. // - To create a popup with a specific identifier, pass it in str_id. -// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. +// - This is useful when using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. // - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id. // - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). // This is essentially the same as: @@ -12849,9 +12958,9 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flag ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "void_context"; - ImGuiID id = window->GetID(str_id); + ImGuiID id = window->GetID(str_id); // FIXME: Use a global ID? ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if (IsMouseReleased(mouse_button, id) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) if (GetTopMostPopupModal() == NULL) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); @@ -14147,6 +14256,7 @@ void ImGui::NavInitRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = 0; g.NavJustMovedToIsTabbing = false; + g.NavJustMovedToIsInit = true; g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } @@ -14414,6 +14524,7 @@ void ImGui::NavMoveRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = g.NavMoveKeyMods; g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + g.NavJustMovedToIsInit = false; g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } @@ -15330,15 +15441,9 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) const bool draw_target_rect = payload.Preview && !(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); if (draw_target_rect && g.DragDropTargetFullViewport != 0) - { - ImRect bb = g.DragDropTargetRect; - bb.Expand(-3.5f); - RenderDragDropTargetRectEx(GetForegroundDrawList(), bb, g.Style.DragDropTargetRounding); - } + RenderDragDropTargetRectForViewport(g.DragDropTargetFullViewport, g.DragDropTargetRect); else if (draw_target_rect) - { RenderDragDropTargetRectForItem(r); - } g.DragDropAcceptFrameCount = g.FrameCount; if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) @@ -15369,6 +15474,17 @@ void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb) window->DrawList->PopClipRect(); } +void ImGui::RenderDragDropTargetRectForViewport(ImGuiID viewport_id, const ImRect& bb) +{ + IM_ASSERT(viewport_id != 0); + IM_UNUSED(viewport_id); // Unused in this branch + ImGuiContext& g = *GImGui; + ImGuiViewport* viewport = g.Viewports[0]; + ImRect bb_padded = bb; + bb_padded.Expand(-g.Style.DragDropTargetPadding); + RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb_padded, g.Style.DragDropTargetRounding); +} + void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding) { ImGuiContext& g = *GImGui; @@ -16072,13 +16188,14 @@ void ImGuiPlatformIO::ClearPlatformHandlers() Platform_OpenInShellUserData = NULL; Platform_SetImeDataFn = NULL; Platform_ImeUserData = NULL; + InitializeDefaultPlatformHandlers(*this); // Rrevents losing the functionality provided by Platform_*_DefaultImpl on backend re-creation. } void ImGuiPlatformIO::ClearRendererHandlers() { Renderer_TextureMaxWidth = Renderer_TextureMaxHeight = 0; Renderer_RenderState = NULL; - DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = NULL; + DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = DrawCallback_SetSamplerFromTex = NULL; } ImGuiViewport* ImGui::GetMainViewport() @@ -16212,7 +16329,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t ::CloseClipboard(); } -#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) +#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) #include // Use old API to avoid need for separate .mm file static PasteboardRef main_clipboard = 0; @@ -16384,6 +16501,15 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG #endif // Default IME handlers +// Setup default ImGuiPlatformIO clipboard/IME handlers +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io) +{ + platform_io.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; + platform_io.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; + platform_io.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; + platform_io.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; +} + //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- @@ -16611,7 +16737,7 @@ static const char* FormatTextureRefForDebugDisplay(char* buf, int buf_size, ImTe char* buf_end = buf + buf_size; if (tex_ref._TexData != NULL) buf_p += ImFormatString(buf_p, buf_end - buf_p, "#%03d: ", tex_ref._TexData->UniqueID); - ImFormatString(buf_p, buf_end - buf_p, "0x%X", ImGui::DebugTextureIDToU64(tex_ref.GetTexID())); + ImFormatString(buf_p, buf_end - buf_p, "0x%" IM_PRIX64, ImGui::DebugTextureIDToU64(tex_ref.GetTexID())); return buf; } @@ -16663,6 +16789,7 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) { const ImFontLoader* loader_current = atlas->FontLoader; + IM_UNUSED(loader_current); BeginDisabled(!atlas->RendererHasTextures); #ifdef IMGUI_ENABLE_STB_TRUETYPE const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); @@ -16783,7 +16910,7 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRe Checkbox("Show used rect", &cfg->ShowTextureUsedRect); PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); ImVec2 p = GetCursorScreenPos(); - if (tex->WantDestroyNextFrame) + if (tex->Status == ImTextureStatus_WantDestroy || tex->Status == ImTextureStatus_Destroyed) Dummy(ImVec2((float)tex->Width, (float)tex->Height)); else ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); @@ -17585,10 +17712,9 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con Selectable(buf, false); if (fg_draw_list && IsItemHovered()) { - ImDrawListFlags backup_flags = fg_draw_list->Flags; - fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + fg_draw_list->PushDrawFlag(ImDrawFlags_AALines, false); // Disable AA on triangle outlines is more readable for very large and thin triangles. fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); - fg_draw_list->Flags = backup_flags; + fg_draw_list->PopDrawFlag(); } } TreePop(); @@ -17604,8 +17730,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co // Draw wire-frame version of all triangles ImRect clip_rect = draw_cmd->ClipRect; ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - ImDrawListFlags backup_flags = out_draw_list->Flags; - out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + out_draw_list->PushDrawFlag(ImDrawFlags_AALines, false); // Disable AA on triangle outlines is more readable for very large and thin triangles. for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; ) { ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list @@ -17623,7 +17748,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->AddRect(ImTrunc(clip_rect.Min), ImTrunc(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU out_draw_list->AddRect(ImTrunc(vtxs_rect.Min), ImTrunc(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles } - out_draw_list->Flags = backup_flags; + out_draw_list->PopDrawFlag(); } // [DEBUG] Compute mask of inputs with the same codepoint. @@ -17633,7 +17758,8 @@ static int CalcFontGlyphSrcOverlapMask(ImFontAtlas* atlas, ImFont* font, unsigne for (int src_n = 0; src_n < font->Sources.Size; src_n++) { ImFontConfig* src = font->Sources[src_n]; - if (!(src->FontLoader ? src->FontLoader : atlas->FontLoader)->FontSrcContainsGlyph(atlas, src, (ImWchar)codepoint)) + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcGetGlyphIndexFromCodepoint(atlas, src, (ImWchar)codepoint) == 0) continue; mask |= (1 << src_n); count++; @@ -18239,9 +18365,9 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float curr_x = window->DC.CursorPos.x; float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); - window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); - window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); - window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); + window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y1, col, 1.0f); + window->DrawList->AddLineV(curr_x, line_y1, line_y2, col, 1.0f); + window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y2, col, 1.0f); } // Draw last item rect in ForegroundDrawList (so it is always visible) diff --git a/3rdparty/imgui/src/imgui_demo.cpp b/3rdparty/imgui/src/imgui_demo.cpp index 9f841b3c44..13734d316f 100644 --- a/3rdparty/imgui/src/imgui_demo.cpp +++ b/3rdparty/imgui/src/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (demo code) // Help: @@ -87,6 +87,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() // [SECTION] DemoWindowWidgetsLiveEdit() +// [SECTION] DemoWindowWidgetsMixedValues() // [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsProgressBars() @@ -575,6 +576,8 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running."); ImGui::Checkbox("io.ConfigDebugIgnoreFocusLoss", &io.ConfigDebugIgnoreFocusLoss); ImGui::SameLine(); HelpMarker("Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data."); + ImGui::Checkbox("io.ConfigDebugDrawListDefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); + ImGui::SameLine(); HelpMarker("Option to default all ImDrawList to ImDrawFlags_StrokeLegacy mode, mimicking pre-1.93.0 rendering."); ImGui::Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); ImGui::SameLine(); HelpMarker("Option to save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)."); @@ -962,8 +965,9 @@ static void DemoWindowWidgetsBasic() ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::Button("Click"); - ImGui::PopStyleColor(3); + ImGui::PopStyleColor(4); ImGui::PopID(); } @@ -1258,6 +1262,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::OpenPopup("mypicker"); backup_color = color; } + float ref_sz = ImGui::GetFrameHeight(); if (ImGui::BeginPopup("mypicker")) { ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); @@ -1265,11 +1270,12 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::ColorPicker4("##picker", (float*)&color, base_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); ImGui::SameLine(); + ImVec2 color_button_sz(ref_sz * 3.0f, ref_sz * 2.0f); ImGui::BeginGroup(); // Lock X position ImGui::Text("Current"); - ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40)); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, color_button_sz); ImGui::Text("Previous"); - if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40))) + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, color_button_sz)) color = backup_color; ImGui::Separator(); ImGui::Text("Palette"); @@ -1280,7 +1286,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; - if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags, ImVec2(20, 20))) + if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags)) color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! // Allow user to drop colors into each palette entry. Note that ColorButton() is already a @@ -1303,8 +1309,9 @@ static void DemoWindowWidgetsColorAndPickers() IMGUI_DEMO_MARKER("Widgets/Color/ColorButton (simple)"); ImGui::Text("Color button only:"); static bool no_border = false; + const ImVec2 color_button_sz(ref_sz * 2.0f, ref_sz * 2.0f); ImGui::Checkbox("ImGuiColorEditFlags_NoBorder", &no_border); - ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, base_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, base_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), color_button_sz); IMGUI_DEMO_MARKER("Widgets/Color/ColorPicker"); ImGui::SeparatorText("Color picker"); @@ -1451,7 +1458,8 @@ static void DemoWindowWidgetsComboBoxes() filter.Clear(); } ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); - filter.Draw("##Filter", -FLT_MIN); + ImGui::SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##Filter", "Filter (incl -excl)"); for (int n = 0; n < IM_COUNTOF(items); n++) { @@ -1678,12 +1686,13 @@ static void DemoWindowWidgetsDragAndDrop() "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn" }; + float sz = ImGui::GetFrameHeight() * 3.0f; for (int n = 0; n < IM_COUNTOF(names); n++) { ImGui::PushID(n); if ((n % 3) != 0) ImGui::SameLine(); - ImGui::Button(names[n], ImVec2(60, 60)); + ImGui::Button(names[n], ImVec2(sz, sz)); // Our buttons are both drag sources and drag targets here! if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) @@ -2020,7 +2029,7 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) { if (ImGui::TreeNode("Live Edit Flags")) { - IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); + IMGUI_DEMO_MARKER("Widgets/Live Edit Flags"); ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); @@ -2050,6 +2059,66 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsMixedValues() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgetsMixedValues() +{ + if (ImGui::TreeNode("Mixed Values")) + { + IMGUI_DEMO_MARKER("Widgets/Mixed Values"); + // This is designed for advanced property editors which are generally reusable and data-driven. + HelpMarker("Using ImGuiItemFlags_MixedValue."); + + static bool use_liveedit = false; + static float items[3] = { 12.0f, 0.0f, 0.0f }; + float* item_ref = &items[0]; + ImGui::Checkbox("ImGuiItemFlags_LiveEditOnInput", &use_liveedit); + + ImGui::SeparatorText("Scalar/Text Widgets"); + const bool is_mixed = memcmp(&items[0], &items[1], sizeof(float)) != 0 || memcmp(&items[0], &items[2], sizeof(float)) != 0; + + // Demonstrate Drags, Sliders, Inputs + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInput, use_liveedit); + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, is_mixed); + bool edited = false; + edited |= ImGui::DragFloat("DragFloat", item_ref); + edited |= ImGui::SliderFloat("SliderFloat", item_ref, 0.0f, 100.0f); + edited |= ImGui::InputFloat("InputFloat", item_ref, 1.0f); + if (edited) + for (float& item : items) + if (&item != item_ref) + item = *item_ref; + ImGui::PopItemFlag(); + + ImGui::Text("Underlying data:"); + ImGui::InputFloat("item 0 (ref)", &items[0]); + ImGui::InputFloat("item 1", &items[1]); + ImGui::InputFloat("item 2", &items[2]); + ImGui::PopItemFlag(); + + // Demonstrate Checkbox(), RadioButton(), Combo(), ColorEdit4() + ImGui::SeparatorText("Others Widgets"); + ImGui::Text("(note: edits are not applied in this demo)"); // <-- Would need more state tracking. + bool b_on = true, b_off = false; + ImGui::Checkbox("Checkbox On", &b_on); + ImGui::Checkbox("Checkbox Off", &b_off); + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); + ImGui::Checkbox("Checkbox Mixed", &b_off); + ImGui::RadioButton("RadioButton Mixed", true); + ImGui::SameLine(); + ImGui::RadioButton("RadioButton Mixed##2", true); // Showing 2 radio buttons makes the example more clear + int combo_idx = 0; + ImGui::Combo("Combo", &combo_idx, "One\0Two\0Three\0"); + ImVec4 color(0.5f, 0.5f, 0.5f, 0.5f); + ImGui::ColorEdit4("ColorEdit4", &color.x); + ImGui::PopItemFlag(); + + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- @@ -2119,10 +2188,12 @@ static void DemoWindowWidgetsPlotting() static bool animate = true; ImGui::Checkbox("Animate", &animate); + ImVec2 plot_sz(0.0f, ImGui::GetFrameHeight() * 4.0f); + // Plot as lines and plot as histogram static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; ImGui::PlotLines("Frame Times", arr, IM_COUNTOF(arr)); - ImGui::PlotHistogram("Histogram", arr, IM_COUNTOF(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); + ImGui::PlotHistogram("Histogram", arr, IM_COUNTOF(arr), 0, NULL, 0.0f, 1.0f, plot_sz); //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!"); // Fill an array of contiguous float values to plot @@ -2151,7 +2222,7 @@ static void DemoWindowWidgetsPlotting() average /= (float)IM_COUNTOF(values); char overlay[32]; sprintf(overlay, "avg %f", average); - ImGui::PlotLines("Lines", values, IM_COUNTOF(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); + ImGui::PlotLines("Lines", values, IM_COUNTOF(values), values_offset, overlay, -1.0f, 1.0f, plot_sz); } // Use functions to generate output @@ -2169,8 +2240,8 @@ static void DemoWindowWidgetsPlotting() ImGui::SameLine(); ImGui::SliderInt("Sample count", &display_count, 1, 400); float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; - ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); - ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, plot_sz); + ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, plot_sz); ImGui::TreePop(); } @@ -2230,12 +2301,14 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; + static bool item_mixedvalue = false; static bool liveedit_flags_override = false; static ImGuiItemFlags liveedit_flags = 0; ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + ImGui::Checkbox("Item MixedValue", &item_mixedvalue); ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); ImGui::SameLine(); if (!liveedit_flags_override) @@ -2260,6 +2333,8 @@ static void DemoWindowWidgetsQueryingStatuses() static char str[16] = {}; if (item_disabled) ImGui::BeginDisabled(true); + if (item_mixedvalue) + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) @@ -2343,6 +2418,8 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::PopItemFlag(); ImGui::PopItemFlag(); } + if (item_mixedvalue) + ImGui::PopItemFlag(); if (item_disabled) ImGui::EndDisabled(); @@ -3678,11 +3755,13 @@ static void DemoWindowWidgetsTabs() // but they tend to make more sense together) static bool show_leading_button = true; static bool show_trailing_button = true; + static bool show_leading_trailing_tabs = false; ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + ImGui::Checkbox("Show Leading+Trailing TabItem()", &show_leading_trailing_tabs); // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyShrink; + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyMixed; EditTabBarFittingPolicyFlags(&tab_bar_flags); if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) @@ -3697,6 +3776,15 @@ static void DemoWindowWidgetsTabs() ImGui::EndPopup(); } + // Demo Leading/Trailing Tabs + if (show_leading_trailing_tabs) + { + if (ImGui::BeginTabItem("Leading", NULL, ImGuiTabItemFlags_Leading)) + ImGui::EndTabItem(); + if (ImGui::BeginTabItem("Trailing", NULL, ImGuiTabItemFlags_Trailing)) + ImGui::EndTabItem(); + } + // Demo Trailing Tabs: click the "+" button to add a new tab. // (In your app you may want to use a font icon instead of the "+") // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end. @@ -3865,14 +3953,24 @@ static void DemoWindowWidgetsTextFilter() static ImGuiTextFilter filter; ImGui::Text("Filter usage:\n" " \"\" display all lines\n" - " \"xxx\" display lines containing \"xxx\"\n" - " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" - " \"-xxx\" hide lines containing \"xxx\""); - filter.Draw(); - const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (int i = 0; i < IM_COUNTOF(lines); i++) - if (filter.PassFilter(lines[i])) - ImGui::BulletText("%s", lines[i]); + " xxx display lines containing \"xxx\"\n" + " xxx yyy display lines containing \"xxx\" and \"yyy\"\n" + " \"xxx yyy\" display lines containing \"xxx yyy\"\n" + " xxx,yyy display lines containing \"xxx\" or \"yyy\"\n" + " -xxx hide lines containing \"xxx\""); + ImGui::SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##Filter", "Filter (incl -excl)"); + if (ImGui::BeginChild("##items", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 15), ImGuiChildFlags_FrameStyle)) + { + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (const char* item : lines) + if (filter.PassFilter(item)) + ImGui::TextUnformatted(item); + for (const char* item : ExampleNames) + if (filter.PassFilter(item)) + ImGui::TextUnformatted(item); + } + ImGui::EndChild(); ImGui::TreePop(); } } @@ -4402,27 +4500,34 @@ static void DemoWindowWidgetsVerticalSliders() if (ImGui::TreeNode("Vertical Sliders")) { IMGUI_DEMO_MARKER("Widgets/Vertical Sliders"); - const float spacing = 4; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 slider_sz(ImGui::GetFrameHeight(), ImGui::GetFrameHeight() * 9); + + ImGui::PushStyleVarX(ImGuiStyleVar_ItemSpacing, (float)(int)(style.ItemSpacing.x * 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, (float)(int)(slider_sz.x * 0.25f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, (float)(int)(slider_sz.x * 0.25f)); + float spacing = style.ItemSpacing.x; static int int_value = 0; - ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5); + ImGui::VSliderInt("##int", slider_sz, &int_value, 0, 5); ImGui::SameLine(); static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; ImGui::PushID("set1"); for (int i = 0; i < 7; i++) { - if (i > 0) ImGui::SameLine(); + if (i > 0)ImGui::SameLine(); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f)); - ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, ""); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", slider_sz, &values[i], 0.0f, 1.0f, ""); if (ImGui::IsItemActive() || ImGui::IsItemHovered()) ImGui::SetTooltip("%.3f", values[i]); - ImGui::PopStyleColor(4); + ImGui::PopStyleColor(5); ImGui::PopID(); } ImGui::PopID(); @@ -4431,15 +4536,15 @@ static void DemoWindowWidgetsVerticalSliders() ImGui::PushID("set2"); static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; const int rows = 3; - const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows)); + const ImVec2 slider_size_small(slider_sz.x, (float)(int)((slider_sz.y - (rows - 1) * spacing) / rows)); for (int nx = 0; nx < 4; nx++) { - if (nx > 0) ImGui::SameLine(); + if (nx > 0)ImGui::SameLine(); ImGui::BeginGroup(); for (int ny = 0; ny < rows; ny++) { ImGui::PushID(nx * rows + ny); - ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + ImGui::VSliderFloat("##v", slider_size_small, &values2[nx], 0.0f, 1.0f, ""); if (ImGui::IsItemActive() || ImGui::IsItemHovered()) ImGui::SetTooltip("%.3f", values2[nx]); ImGui::PopID(); @@ -4452,15 +4557,17 @@ static void DemoWindowWidgetsVerticalSliders() ImGui::PushID("set3"); for (int i = 0; i < 4; i++) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); ImGui::PushID(i); - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); - ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, slider_sz.y * 0.25f); + ImGui::VSliderFloat("##v", ImVec2(slider_sz.x * 2.0f, slider_sz.y), &values[i], 0.0f, 1.0f, "%.2f\nsec"); ImGui::PopStyleVar(); ImGui::PopID(); } ImGui::PopID(); - ImGui::PopStyleVar(); + + ImGui::PopStyleVar(3); ImGui::TreePop(); } } @@ -4505,6 +4612,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); DemoWindowWidgetsLiveEdit(demo_data); + DemoWindowWidgetsMixedValues(); DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsPlotting(); DemoWindowWidgetsProgressBars(); @@ -8072,7 +8180,8 @@ static void DemoWindowInputs() ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); ImGui::SliderInt("SetNextFrameWantCaptureKeyboard() on hover", &capture_override_keyboard, -1, +1, capture_override_desc[capture_override_keyboard + 1], ImGuiSliderFlags_AlwaysClamp); - ImGui::ColorButton("##panel", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(128.0f, 96.0f)); // Dummy item + float ref_sz = ImGui::GetFrameHeight() * 4.0f; + ImGui::ColorButton("##panel", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(ref_sz, ref_sz)); // Dummy item if (ImGui::IsItemHovered() && capture_override_mouse != -1) ImGui::SetNextFrameWantCaptureMouse(capture_override_mouse == 1); if (ImGui::IsItemHovered() && capture_override_keyboard != -1) @@ -8742,9 +8851,6 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SameLine(); SetNextItemWidth(GetFontSize() * 10); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); SameLine(); Checkbox("Only Modified Colors", &output_only_modified); - static ImGuiTextFilter filter; - filter.Draw("Filter colors", GetFontSize() * 16); - static ImGuiColorEditFlags alpha_flags = 0; if (RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } SameLine(); if (RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } SameLine(); @@ -8754,6 +8860,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "Left-click on color square to open color picker,\n" "Right-click to open edit options menu."); + static ImGuiTextFilter filter; + SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##FilterColors", "Filter Colors (incl -excl)"); + SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); PushItemWidth(GetFontSize() * -12); @@ -8821,15 +8931,25 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) Checkbox("Anti-aliased lines", &style.AntiAliasedLines); SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + BeginDisabled(style.AntiAliasedLines == false); + Checkbox("Anti-aliased line ends", &style.AntiAliasedLineEnds); + EndDisabled(); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + BeginDisabled(); Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); SameLine(); HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); + EndDisabled(); +#endif Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + Checkbox("Debug: DrawList Defaults to _StrokeLegacy", &ImGui::GetIO().ConfigDebugDrawListDefaultsToStrokeLegacy); + PushItemWidth(GetFontSize() * 8); - DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); - if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; + DragFloat("Curve Tessellation Max Error", &style.CurveTessellationMaxError, 0.02f, 0.10f, 10.0f, "%.2f"); + if (style.CurveTessellationMaxError < 0.10f) + style.CurveTessellationMaxError = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); @@ -9165,7 +9285,10 @@ struct ExampleAppConsole if (ImGui::Button("Options")) ImGui::OpenPopup("Options"); ImGui::SameLine(); - Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); + ImGui::SetNextItemWidth(-FLT_MIN); + Filter.DrawWithHint("##Filter", "Filter (incl -excl)"); ImGui::Separator(); // Reserve enough left-over height for 1 separator + 1 input text @@ -9500,7 +9623,8 @@ struct ExampleAppLog ImGui::SameLine(); bool copy = ImGui::Button("Copy"); ImGui::SameLine(); - Filter.Draw("Filter", -100.0f); + ImGui::SetNextItemWidth(-FLT_MIN); + Filter.DrawWithHint("##Filter", "Filter (incl -excl)"); ImGui::Separator(); @@ -9695,7 +9819,7 @@ struct ExampleAppPropertyEditor ImGui::Text("(%d root nodes)", root_node->Childs.Size); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); - if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) + if (ImGui::InputTextWithHint("##Filter", "incl -excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) Filter.Build(); ImGui::PopItemFlag(); @@ -10236,13 +10360,10 @@ static void ShowExampleAppWindowTitles(bool*) // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- -// Add a |_| looking shape -static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) -{ - const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; - for (const ImVec2& p : pos_norms) - draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); -} +// See our Wiki guides: +// - ImDrawList Vector Rendering Reference: https://github.com/ocornut/imgui/wiki/Draw-List +// - Pixel Perfect Anti-aliased Rendering: https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering +// - How Anti-aliased Polyline Rendering is Implemented: https://github.com/ocornut/imgui/wiki/Polyline-Rendering // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) @@ -10274,118 +10395,250 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Text("Gradients"); ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight()); { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImGui::InvisibleButton("##gradient1", gradient_size); ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient1", gradient_size); + draw_list->AddRectFilledMultiColor(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), col_a, col_b, col_b, col_a); } { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImGui::InvisibleButton("##gradient2", gradient_size); ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255)); ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient2", gradient_size); + draw_list->AddRectFilledMultiColor(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), col_a, col_b, col_b, col_a); } // Draw a bunch of primitives ImGui::Text("All primitives"); static float sz = 42.0f; - static float thickness = 3.0f; - static int ngon_sides = 6; + static float base_rounding = 8.0f; + static float base_thickness = 3.0f; + static bool animate_rounding = false; + static bool animate_thickness = false; + static int ngon_segments = 6; static bool circle_segments_override = false; static int circle_segments_override_v = 12; static bool curve_segments_override = false; static int curve_segments_override_v = 8; - static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); - ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); - ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 40); - ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); - ImGui::ColorEdit4("Color", &colf.x); + static ImVec4 colf_stroke = ImVec4(1.000f, 0.384f, 0.169f, 1.000f); + static ImVec4 colf_fill = ImVec4(0.416f, 0.378f, 0.420f, 1.000f); - const ImVec2 p = ImGui::GetCursorScreenPos(); - const ImU32 col = ImColor(colf); - const float spacing = 10.0f; - const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; - const float rounding = sz / 5.0f; + ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); + + const float rounding_anim_period = 3.0f; + const float rounding_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), rounding_anim_period) * (2.0f / rounding_anim_period) - 1.0f); + const float rounding = animate_rounding ? (float)(int)(rounding_wave * base_rounding * 10.0f) / 10.0f : base_rounding; // Sub-integer rounding works but has discontinuities in small values, so best disable we + ImGui::DragFloat("Rounding", &base_rounding, 0.02f, 0.0f, 32.0f, "%.1f"); ImGui::SameLine(); + ImGui::Checkbox("Animate##rounding", &animate_rounding); ImGui::SameLine(); + ImGui::Text("%.2f", rounding); + + const float thickness_anim_period = 5.0f; + const float thickness_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); + const float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; + ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 32.0f, "%.02f"); ImGui::SameLine(); + ImGui::Checkbox("Animate##thickness", &animate_thickness); ImGui::SameLine(); + ImGui::Text("%.2f", thickness); + + ImGui::SliderInt("N-gon sides", &ngon_segments, 3, 12); + ImGui::Checkbox("##CircleSegmentOverride", &circle_segments_override); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 30); + ImGui::Checkbox("##CurvesSegmentOverride", &curve_segments_override); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 30); + ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); + ImGui::ColorEdit4("Fill Color", &colf_fill.x); + + static ImDrawFlags prim_stroke_flags = ImDrawFlags_None; + static ImDrawFlags prim_other_flags = ImDrawFlags_AALineEnds; + ImGui::SeparatorText("Per primitive flags (AddXXX functions)"); + ImGui::RadioButton("Default", &prim_stroke_flags, ImDrawFlags_None); ImGui::SameLine(); + ImGui::RadioButton("Inside", &prim_stroke_flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); + ImGui::RadioButton("Center", &prim_stroke_flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); + ImGui::RadioButton("CenterBiased", &prim_stroke_flags, ImDrawFlags_StrokeCenterBiased); ImGui::SameLine(); + ImGui::RadioButton("Outside", &prim_stroke_flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); + ImGui::RadioButton("Legacy", &prim_stroke_flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("AAFill", &prim_other_flags, ImDrawFlags_AAFill); ImGui::SameLine(); // Note: Only for functions taking flags. + ImGui::CheckboxFlags("AALines", &prim_other_flags, ImDrawFlags_AALines); ImGui::SameLine(); // Note: Only for functions taking flags. + ImGui::CheckboxFlags("AALineEnds", &prim_other_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); + ImGui::CheckboxFlags("JoinMiter", &prim_other_flags, ImDrawFlags_JoinMiter); ImGui::SameLine(); + ImGui::CheckboxFlags("CapSquare", &prim_other_flags, ImDrawFlags_CapSquare); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); + + static ImDrawFlags scope_flags = draw_list->Flags & ImDrawFlags_AllowInPushScope_; // First init copy current state. + ImGui::SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)"); + ImGui::PushID("ScopeFlags"); + ImGui::CheckboxFlags("AAFill", &scope_flags, ImDrawFlags_AAFill); ImGui::SameLine(); + ImGui::CheckboxFlags("AALines", &scope_flags, ImDrawFlags_AALines); ImGui::SameLine(); + ImGui::CheckboxFlags("AALineEnds", &scope_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); + ImGui::CheckboxFlags("StrokeLegacy", &scope_flags, ImDrawFlags_StrokeLegacy); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); + ImGui::PopID(); + ImGui::Spacing(); + + draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) + draw_list->PushDrawFlag(scope_flags, true); + const ImDrawFlags flags = prim_stroke_flags | prim_other_flags; + + const ImVec2 start_pos = ImGui::GetCursorScreenPos(); + const float pi = 3.141592f; + const float step = sz + 10.0f; + const ImDrawFlags corners_tl_br = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundBottomRight; + const float half_sz = sz * 0.5f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; - const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves - const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) }; - - float x = p.x + 4.0f; - float y = p.y + 4.0f; - for (int n = 0; n < 2; n++) + const ImVec2 cp3[3] = { { 0.0f, sz * 0.6f }, { sz * 0.5f, -sz * 0.4f }, { sz, sz } }; // Control points for curves + const ImVec2 cp4[4] = { { 0.0f, 0.0f }, { sz * 1.3f, sz * 0.3f }, { sz - sz * 1.3f, sz - sz * 0.3f }, { sz, sz } }; + const ImVec2 concave_shape[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; // |_| looking shape. Normalized. + const ImVec2 zigzag_shape[] = { { 0.0f, 0.0f }, { 0.9f, 0.0f }, { 1.0f, 0.1f }, { 1.0f, 0.9f }, { 0.9f, 1.0f }, { 0.3f, 1.0f }, { 0.3f, 0.4f }, { 0.9f, 0.4f } }; + ImVec2 rotating_square[4]; + for (int side = 0; side < 4; side++) { - // First line uses a thickness of 1.0f, second line uses the configurable thickness - float th = (n == 0) ? 1.0f : thickness; - draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon - draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle - //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, ImDrawFlags_Closed); x += sz + spacing; // Concave Shape - //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); - draw_list->AddLineH(x, x + sz, y, col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLineV(x, y, y + sz, col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line - - // Path - draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, th); - x += sz + spacing; - - // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments); - x += sz + spacing; - - // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments); - - x = p.x + 4; - y += sz + spacing; + float t = (float)ImGui::GetTime() * 0.1f + side * pi * 0.5f; + rotating_square[side] = { (float)cosf(t) * half_sz, (float)sinf(t) * half_sz }; } - // Filled shapes - draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon - draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), ImVec2(sz * 0.5f, sz * 0.3f), col, -0.3f, circle_segments); x += sz + spacing;// Ellipse - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle - //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) + float x = start_pos.x; + float y = start_pos.y; + for (int row = 0; row < 4; row++) + { + x = start_pos.x; + bool draw_fill = (row == 2 || row == 3); + bool draw_strokes = (row == 0 || row == 1 || row == 3); + if (draw_fill) + { + // FILLED SHAPES + const ImU32 col = ImColor(colf_fill); + draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments); x += step; // N-gon + draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += step; // Circle + draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { half_sz, sz * 0.3f }, col, -0.3f, circle_segments); x += step;// Ellipse + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, 0.0f, flags); x += step; // Square + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, flags); x += step; // Square with all rounded corners + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, flags | corners_tl_br); x += step; // Square with two rounded corners + draw_list->AddTriangleFilled({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col); x += step; // Triangle + draw_list->AddTriangleFilled({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col); x += step - (float)(int)(sz * 0.6f); // Thin triangle - // Path - draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f); - draw_list->PathFillConvex(col); - x += sz + spacing; + // Concave shape: U + for (const ImVec2& p : concave_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathFillConcave(col); + x += step; - // Quadratic Bezier Curve (3 control points) - draw_list->PathLineTo(ImVec2(x + cp3[0].x, y + cp3[0].y)); - draw_list->PathBezierQuadraticCurveTo(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), curve_segments); - draw_list->PathFillConvex(col); - x += sz + spacing; + // Concave shape: Zig-Zag with sharp angles (see difference with/without JoinMiter) + for (const ImVec2& p : zigzag_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathFillConcave(col); + x += step; - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); - x += sz + spacing; + // Horizontal/Vertical lines + // (here for consistency with the strokes section. AddRectFilled() is roughly close to AddLineH(), AddLineV() + draw_list->AddRectFilled({ x, y }, { x + sz, y + thickness }, col); x += step; + draw_list->AddRectFilled({ x, y }, { x + thickness, y + sz }, col); x += step - (float)(int)half_sz; + if (!(draw_fill && draw_strokes)) //-V560 + for (int block_sz = 1, off = 0; block_sz < 16 && off + block_sz <= sz; off += block_sz + 1, block_sz++) + draw_list->AddRectFilled({ x, y + off }, { x + block_sz, y + off + block_sz }, col); + x += step - (float)(int)half_sz; + + // Rotating square + for (int n = 0; n < 4; n++) + draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); + draw_list->PathFillConvex(col, flags); + x += step; + + // Path + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi * 1.1f); + draw_list->PathFillConvex(col); + x += step; + + // Quadratic Bezier Curve (3 control points) + draw_list->PathLineTo({ x + cp3[0].x, y + cp3[0].y }); + draw_list->PathBezierQuadraticCurveTo({ x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, curve_segments); + draw_list->PathFillConvex(col); + x += step; + + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddRectFilledMultiColor({ x, y }, { x + sz, y + sz }, IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + x += step; + } + } + if (draw_fill && draw_strokes) + x = start_pos.x; + if (draw_strokes) + { + // STROKES + // First row uses a thickness of 1.0f, second row uses the configurable thickness + const ImU32 col = ImColor(colf_stroke); + float th = (row == 0) ? 1.0f : thickness; + draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments, th, flags); x += step; // N-gon + draw_list->AddCircle({ x + half_sz, y + half_sz }, half_sz, col, circle_segments, th, flags); x += step; // Circle + draw_list->AddEllipse({ x + half_sz, y + half_sz }, { half_sz, sz * 0.3f }, col, -0.3f, circle_segments, th, flags); x += step; // Ellipse + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, 0.0f, th, flags); x += step; // Square + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags); x += step; // Square with all rounded corners + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags | corners_tl_br); x += step; // Square with two rounded corners + draw_list->AddTriangle({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col, th, flags); x += step; // Triangle + draw_list->AddTriangle({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col, th, flags); x += step - (float)(int)(sz * 0.6f); // Thin triangle + + // Concave shape: U + for (const ImVec2& p : concave_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); + x += step; + + // Concave shape: Zig-Zag with sharp angles (see difference with/without JoinMiter) + for (const ImVec2& p : zigzag_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); + x += step; + + // Horizontal/Vertical/Diagonal lines + const float off = (float)(int)(sz * 0.4f); + draw_list->AddLineH(x, x + sz, y, col, th, flags); + draw_list->AddLine({ x, y + off }, { x + sz, y + off }, col, th, flags); + x += step; + draw_list->AddLineV(x, y, y + sz, col, th, flags); + draw_list->AddLine({ x + off, y + sz }, { x + off, y }, col, th, flags); + x += step - (float)(int)half_sz; + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddLine({ x, y }, { x + half_sz, y + sz }, col, th, flags); + draw_list->AddLine({ x, y + sz }, { x + half_sz, y }, col, th, flags); + } + x += step - (float)(int)half_sz; + + // Rotating square + for (int n = 0; n < 4; n++) + draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); + //for (int n = 0; n < 4; n++) // ...made of individual line + // draw_list->AddLine({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }, { x + half_sz + rotating_square[(n + 1) & 3].x, y + half_sz + rotating_square[(n + 1) & 3].y }, col, th, flags); + x += step; + + // Path + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi * 1.1f); + draw_list->PathStroke(col, th, flags); + x += step; + + // Quadratic Bezier Curve (3 control points) + draw_list->AddBezierQuadratic({ x + cp3[0].x, y + cp3[0].y }, { x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, col, th, curve_segments, flags); + x += step; + + // Cubic Bezier Curve (4 control points) + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddBezierCubic({ x + cp4[0].x, y + cp4[0].y }, { x + cp4[1].x, y + cp4[1].y }, { x + cp4[2].x, y + cp4[2].y }, { x + cp4[3].x, y + cp4[3].y }, col, th, curve_segments, flags); + x += step; + } + } + y += step; + } + + ImGui::Dummy(ImVec2(step * 15.5f, step * 4.0f)); + draw_list->PopDrawFlag(); + draw_list->PopDrawFlag(); + + ImGui::Text("ImDrawList Vector Rendering Reference:"); + ImGui::TextLinkOpenURL("https://github.com/ocornut/imgui/wiki/Draw-List"); - ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); @@ -10519,9 +10772,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Text("Blue shape is drawn first: appears in back"); ImGui::Text("Red shape is drawn after: appears in front"); ImVec2 p0 = ImGui::GetCursorScreenPos(); - draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), IM_COL32(0, 0, 255, 255)); // Blue - draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), ImVec2(p0.x + 75, p0.y + 75), IM_COL32(255, 0, 0, 255)); // Red - ImGui::Dummy(ImVec2(75, 75)); + draw_list->AddRectFilled({ p0.x, p0.y }, { p0.x + 50, p0.y + 50 }, IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled({ p0.x + 25, p0.y + 25 }, { p0.x + 75, p0.y + 75 }, IM_COL32(255, 0, 0, 255)); // Red + ImGui::Dummy({ 75, 75 }); } ImGui::Separator(); { @@ -10533,14 +10786,14 @@ static void ShowExampleAppCustomRendering(bool* p_open) // You can create any number of channels. Tables API use 1 channel per column in order to better batch draw calls. draw_list->ChannelsSplit(2); draw_list->ChannelsSetCurrent(1); - draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled({ p1.x, p1.y }, { p1.x + 50, p1.y + 50 }, IM_COL32(0, 0, 255, 255)); // Blue draw_list->ChannelsSetCurrent(0); - draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), ImVec2(p1.x + 75, p1.y + 75), IM_COL32(255, 0, 0, 255)); // Red + draw_list->AddRectFilled({ p1.x + 25, p1.y + 25 }, { p1.x + 75, p1.y + 75 }, IM_COL32(255, 0, 0, 255)); // Red // Flatten/reorder channels. Red shape is in channel 0 and it appears below the Blue shape in channel 1. // This works by copying draw indices only (vertices are not copied). draw_list->ChannelsMerge(); - ImGui::Dummy(ImVec2(75, 75)); + ImGui::Dummy({ 75, 75 }); ImGui::Text("After reordering, contents of channel 0 appears below channel 1."); } ImGui::EndTabItem(); diff --git a/3rdparty/imgui/src/imgui_draw.cpp b/3rdparty/imgui/src/imgui_draw.cpp index 2eed77033b..f07659ba53 100644 --- a/3rdparty/imgui/src/imgui_draw.cpp +++ b/3rdparty/imgui/src/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (drawing and font code) /* @@ -105,7 +105,7 @@ namespace IMGUI_STB_NAMESPACE #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration #pragma warning (disable: 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. -#pragma warning (disable: 5262) // (stb_truetype) implicit fall-through occurs here; are you missing a break statement? +#pragma warning (disable: 5262) // (stb_truetype) implicit fall-through occurs here; are you missing a break statement? #pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. #pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. #endif @@ -190,15 +190,15 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) ImVec4* colors = style->Colors; colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.58f, 0.58f, 0.58f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_FrameBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.25f, 0.31f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.21f, 0.33f, 0.47f, 1.00f); colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); @@ -235,9 +235,9 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); - colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); // Prefer using Alpha=1.0 here - colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.28f, 0.28f, 0.29f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.40f, 0.40f, 0.45f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.32f, 0.32f, 0.34f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; @@ -336,9 +336,9 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_TitleBg] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.43f); colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); @@ -396,7 +396,6 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) ImDrawListSharedData::ImDrawListSharedData() { memset((void*)this, 0, sizeof(*this)); - InitialFringeScale = 1.0f; for (int i = 0; i < IM_COUNTOF(ArcFastVtx); i++) { const float a = ((float)i * 2 * IM_PI) / (float)IM_COUNTOF(ArcFastVtx); @@ -462,7 +461,8 @@ void ImDrawList::_ResetForNewFrame() CmdBuffer.resize(0); IdxBuffer.resize(0); VtxBuffer.resize(0); - Flags = _Data->InitialFlags; + Flags = _Data->InitialDrawFlags; + IM_ASSERT((Flags & ~ImDrawFlags_AllowInFrameScope_) == 0); memset(&_CmdHeader, 0, sizeof(_CmdHeader)); _VtxCurrentIdx = 0; _VtxWritePtr = NULL; @@ -470,10 +470,11 @@ void ImDrawList::_ResetForNewFrame() _ClipRectStack.resize(0); _TextureStack.resize(0); _CallbacksDataBuf.resize(0); + _DrawFlagsStack.clear(); _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); - _FringeScale = _Data->InitialFringeScale; + // Caller needs to bet _SetPixelDensity() as well. } void ImDrawList::_ClearFreeMemory() @@ -481,13 +482,14 @@ void ImDrawList::_ClearFreeMemory() CmdBuffer.clear(); IdxBuffer.clear(); VtxBuffer.clear(); - Flags = ImDrawListFlags_None; + Flags = ImDrawFlags_None; _VtxCurrentIdx = 0; _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.clear(); _TextureStack.clear(); _CallbacksDataBuf.clear(); + _DrawFlagsStack.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); } @@ -655,6 +657,7 @@ void ImDrawList::_OnChangedVtxOffset() int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { // Automatic segment count + radius *= _InvFringeScale; const int radius_idx = (int)(radius + 0.999f); // ceil to never reduce accuracy if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts)) return _Data->CircleSegmentCounts[radius_idx]; // Use cached value @@ -699,7 +702,7 @@ void ImDrawList::PushTexture(ImTextureRef tex_ref) _TextureStack.push_back(tex_ref); _CmdHeader.TexRef = tex_ref; if (tex_ref._TexData != NULL) - IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); + IM_ASSERT(tex_ref._TexData->Status != ImTextureStatus_WantDestroy && tex_ref._TexData->Status != ImTextureStatus_Destroyed); _OnChangedTexture(); } @@ -710,6 +713,26 @@ void ImDrawList::PopTexture() _OnChangedTexture(); } +// IMPORTANT: currently limited to 3 deep +void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) +{ + IM_ASSERT((flags & ~ImDrawFlags_AllowInPushScope_) == 0); + IM_ASSERT((flags & ImDrawFlags_StrokeMask_) == 0 || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy); + IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size < _DrawFlagsStack.capacity(), "PushDrawFlag() has a depth limit. Contact us."); + _DrawFlagsStack.push_back(Flags); + if (enabled) + Flags |= flags; + else + Flags &= ~flags; +} + +void ImDrawList::PopDrawFlag() +{ + IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size > 0, "Calling PopDrawFlag() too many times!"); + Flags = _DrawFlagsStack.back(); + _DrawFlagsStack.pop_back(); +} + // This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). void ImDrawList::_SetTexture(ImTextureRef tex_ref) { @@ -720,6 +743,15 @@ void ImDrawList::_SetTexture(ImTextureRef tex_ref) _OnChangedTexture(); } +void ImDrawList::_SetPixelDensity(float pixel_density) +{ + IM_ASSERT(pixel_density > 0.0f); + _FringeScale = 1.0f / pixel_density; + _InvFringeScale = pixel_density; + _FringeScaleIsInteger = ImIsTruncated(_InvFringeScale); +} + +IM_MSVC_RUNTIME_CHECKS_OFF // Reserve space for a number of vertices and indices. // You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or // submit the intermediate results. PrimUnreserve() can be used to release unused allocations. @@ -727,7 +759,7 @@ void ImDrawList::PrimReserve(int idx_count, int vtx_count) { // Large mesh support (when enabled) IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); - if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawListFlags_AllowVtxOffset)) + if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawFlags_UseVtxOffset)) { // FIXME: In theory we should be testing that vtx_count <64k here. // In practice, RenderText() relies on reserving ahead for a worst case scenario so it is currently useful for us @@ -759,6 +791,26 @@ void ImDrawList::PrimUnreserve(int idx_count, int vtx_count) IdxBuffer.shrink(IdxBuffer.Size - idx_count); } +// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. +// We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. +#define IM_APPEND_VTX(PX, PY, UV, COL) \ + { \ + _VtxWritePtr->pos.x = PX; \ + _VtxWritePtr->pos.y = PY; \ + _VtxWritePtr->uv = UV; \ + _VtxWritePtr->col = COL; \ + _VtxWritePtr++; \ + _VtxCurrentIdx++; \ + } (void)0 + +#define IM_APPEND_TRI(a, b, c) \ + { \ + _IdxWritePtr[0] = (ImDrawIdx)(a); \ + _IdxWritePtr[1] = (ImDrawIdx)(b); \ + _IdxWritePtr[2] = (ImDrawIdx)(c); \ + _IdxWritePtr += 3; \ + } (void)0 + // Fully unrolled with inline call to keep our debug builds decently fast. void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) { @@ -804,6 +856,24 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _IdxWritePtr += 6; } +float ImDrawList::_CalculateCenterBiasedOffset(float thickness) +{ + // Old jumpy one. + // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + // Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased + // so that one side the of the line is always at pixel boundary. + // On integer thickness both sides of the line are on pixel boundary. + const float screen_thickness = thickness * _InvFringeScale; + const int s_thickness = (int)screen_thickness; + return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + +// Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +// FIXME-OPT: Given unused padding space in ImDrawList would it make sense to store 0.25f * _FringeScale? +#define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) + // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. // - Those macros expects l-values and need to be used as their own statement. // - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. @@ -811,357 +881,650 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 -// TODO: Thickness anti-aliased lines cap are missing their AA fringe. -// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +// - The miter limit describes how much the miter cross section can be scaled before we turn it into a bevel instead. +// The minimum value of miter limit is 1.0, which translates to 180 (all corners are bevels), the higher the number, +// the closer to zero the angle is (never bevel a corner). The miter limit angle can be calculated as: +// miter_limit_angle = 2 * acos(1 / miter_limit) +// - We can use the miter limit angle to figure maximum number of beveled corners in a convex polygon, +// to prevent having allocate much extra memory (for a reasonable miter limit). +// (n - 2) * 360 > k * miter_limit_angle + (n - k) * 360 +// Where, n = number of corners in a polygon, k = beveled corners. Solved for k, simplifies to: +// k < 360 / (180 - miter_limit_angle) +// To find value strictly less than: +// max_bevels = k = floor(360/(180-miter_limit_angle)) +#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // Safeguard for miter corner calculation to avoid div by zero. cos(179.5) +#define IM_POLYLINE_MITER_LIMIT (4.0f) // (~29 deg corner) +#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // For miter value 4.0 + +// In debug builds the functions are likely not inlined, so inlined check is cheaper. +#if defined(DEBUG) || defined(_DEBUG) +#define IM_IS_TRUNCATED4(a, b, c, d) ((float)(int)(a) == (a)) && ((float)(int)(b) == (b)) && ((float)(int)(c) == (c)) && ((float)(int)(d) == (d)) +#define IM_IS_TRUNCATED(a) ((float)(int)(a) == (a)) +#else +#define IM_IS_TRUNCATED4(a, b, c, d) ImIsTruncated4(a, b, c, d) +#define IM_IS_TRUNCATED(a) ImIsTruncated(a) +#endif + +IM_MSVC_RUNTIME_CHECKS_OFF +static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) +{ + IM_ASSERT_PARANOID(alpha_mul >= 0.0f && alpha_mul < 1.0f); // We don't clamp! + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// Select texture and adjust fringe size for specified line thickness. +void ImDrawList::_SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImVec2* out_uv1, float* out_fringe, ImDrawFlags flags) +{ + // We assume that the screen_thickness has been clamped to 1.0 by the caller. + IM_ASSERT_PARANOID(screen_thickness >= 1.0f); + + if (flags & ImDrawFlags_AALines) + { + // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + // We use super sampled textures in this range to make texture changes less noticeable. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT-1 maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + int texture_idx; + if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY + texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + else if (screen_thickness < (float)IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1.5f) IM_LIKELY + texture_idx = (int)(screen_thickness + 0.5f); + else + texture_idx = IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1; // Catches large values of screen_thickness (and +inf/NaN) that a ImMin() wouldn't. + + // Scale the fringe so that the texture matches the line thickness. + ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + out_uv0->x = tex_uvs.x; + out_uv1->x = tex_uvs.y; + out_uv0->y = out_uv1->y = tex_uvs.z; + *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness + } + else + { + // For no anti-alias we use fully opaque texture, and no fringe expansion. + *out_uv0 = *out_uv1 = _Data->TexUvWhitePixel; + *out_fringe = 0.0f; + } +} + +#define _GetStrokePos(_FLAGS, _DEFAULT_STROKE_POS) (((_FLAGS) & ImDrawFlags_StrokeMask_) ? ((_FLAGS) & ImDrawFlags_StrokeMask_) : _DEFAULT_STROKE_POS) + +IM_MSVC_RUNTIME_CHECKS_RESTORE + +void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_offset) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; - const bool closed = (flags & ImDrawFlags_Closed) != 0; - const ImVec2 opaque_uv = _Data->TexUvWhitePixel; - const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw - const bool thick_line = (thickness > _FringeScale); + float screen_thickness = thickness * _InvFringeScale; + if (screen_thickness < 1.0f) IM_UNLIKELY + { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } // If this assert triggers on legacy code: // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + flags |= Flags; IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); - if (Flags & ImDrawListFlags_AntiAliasedLines) + // Allocate data for temp buffers + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + + // Calculate normals for each line segment + for (int i = 0; i < points_count - 1; i++) { - // Anti-aliased stroke - const float AA_SIZE = _FringeScale; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; + float dx = points[i + 1].x - points[i].x; + float dy = points[i + 1].y - points[i].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + normals[i].x = -dy * inv_len; + normals[i].y = dx * inv_len; + sqr_lengths[i] = d2; + } + if ((flags & ImDrawFlags_Closed) != 0) + { + const float dx = points[0].x - points[points_count - 1].x; + const float dy = points[0].y - points[points_count - 1].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + normals[points_count - 1].x = -dy * inv_len; + normals[points_count - 1].y = dx * inv_len; + sqr_lengths[points_count - 1] = d2; + } + else + { + normals[points_count - 1] = normals[points_count - 2]; + sqr_lengths[points_count - 1] = 0.0f; + } - // Thicknesses <1.0 should behave like thickness 1.0 - thickness = ImMax(thickness, 1.0f); - const int integer_thickness = (int)thickness; - const float fractional_thickness = thickness - integer_thickness; + ImVec2 uv0, uv1; + float fringe; + _SelectLineTexture(screen_thickness, &uv0, &uv1, &fringe, flags); - // Do we want to draw this line using a texture? - // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. - // - If AA_SIZE is not 1.0f we cannot use the texture path. - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); + const ImU32 col_trans = col & ~IM_COL32_A_MASK; - // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off - IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + flags = (flags | ImDrawFlags_JoinMiter) & ~ImDrawFlags_AALineEnds; - const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); - const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); - PrimReserve(idx_count, vtx_count); + const bool closed = (flags & ImDrawFlags_Closed) != 0; + const bool miters_only = (flags & ImDrawFlags_JoinMiter) != 0; + const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; - // Temporary buffer - // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - _Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5)); - ImVec2* temp_normals = _Data->TempBuffer.Data; - ImVec2* temp_points = temp_normals + points_count; + float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) + if (stroke_pos == ImDrawFlags_StrokeOutside) + thickness0 = thickness + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + thickness0 = fringe * 0.5f; + float thickness1 = (thickness + fringe) - thickness0; - // Calculate normals (tangents) for each line segment - for (int i1 = 0; i1 < count; i1++) + int idx_count = 0; + int vtx_count = 0; + const int max_verts_per_point = miters_only ? 2 : 4; + const int max_tris_per_point = miters_only ? 2 : 5; + if (closed) + { + vtx_count = /*body*/points_count * max_verts_per_point + /*closing*/2; + idx_count = (/*body*/points_count * max_tris_per_point + /*closing*/2) * 3; + } + else + { + vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(4 * 2); + idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/2 + /*caps*/(2 * 2)) * 3; + } + PrimReserve(idx_count, vtx_count); + + const float ratio = thickness0 / (thickness0 + thickness1); // The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. + const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, uv0.y); + + // Clamp inner offset and adjust texture coordinates to match the change. + // This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. + if (thickness1 > max_inner_offset) + { + thickness1 = max_inner_offset; + const float s = (thickness0 + thickness1) / (thickness + fringe); + const float du = uv1.x - uv0.x; + uv1.x = uv0.x + du * s; + } + + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. + const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); + + const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); + int base_idx = (int)_VtxCurrentIdx; + + ImVec2 pa, pb, dir; + ImVec2 p1; + ImVec2 n1; + float len_sqr1; + + int point_idx = 0; + int point_end = points_count; + if (!closed) + { + // Start cap + point_idx++; + point_end--; + p1 = points[0]; + n1 = normals[0]; + len_sqr1 = sqr_lengths[0]; + + dir.x = n1.y; + dir.y = -n1.x; + if (flags & ImDrawFlags_CapSquare) { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; - float dx = points[i2].x - points[i1].x; - float dy = points[i2].y - points[i1].y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - temp_normals[i1].x = dy; - temp_normals[i1].y = -dx; + p1.x -= dir.x * thickness * 0.5f; + p1.y -= dir.y * thickness * 0.5f; } - if (!closed) - temp_normals[points_count - 1] = temp_normals[points_count - 2]; - // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - if (use_texture || !thick_line) + if (!use_aa_ends) { - // [PATH 1] Texture-based lines (thick or non-thick) - // [PATH 2] Non texture-based lines (non-thick) + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); + } + else + { + pa.x = p1.x - dir.x * half_aa; + pa.y = p1.y - dir.y * half_aa; + pb.x = p1.x + dir.x * half_aa; + pb.y = p1.y + dir.y * half_aa; - // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA. - // - In the texture-based path, we don't use AA_SIZE here because the +1 is tied to the generated texture - // (see ImFontAtlasBuildRenderLinesTexData() function), and so alternate values won't work without changes to that code. - // - In the non texture-based paths, we would allow AA_SIZE to potentially be != 1.0f with a patch (e.g. fringe_scale patch to - // allow scaling geometry while preserving one-screen-pixel AA fringe). - const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : AA_SIZE; + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (!closed) + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + } + } + else + { + // Wrap around segment + p1 = points[points_count - 1]; + n1 = normals[points_count - 1]; + len_sqr1 = sqr_lengths[points_count - 1]; + + // This will be filled later, allocate space. + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, uv0, col); + IM_APPEND_VTX(0, 0, uv1, col); + } + + if (miters_only) + { + while (point_idx < point_end) + { + ImVec2 n0 = n1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + + base_idx = next_base_idx; + + point_idx++; + } + } + else + { + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); + float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + if (miter_distance_sqr > miter_distance_limit_sqr) { - temp_points[0] = points[0] + temp_normals[0] * half_draw_size; - temp_points[1] = points[0] - temp_normals[0] * half_draw_size; - temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * half_draw_size; - temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * half_draw_size; - } + // Clipped bevel - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment - for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment - { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment - const unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of segment - - // Average normals - float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; - float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area - dm_y *= half_draw_size; - - // Add temporary vertices for the outer edges - ImVec2* out_vtx = &temp_points[i2 * 2]; - out_vtx[0].x = points[i2].x + dm_x; - out_vtx[0].y = points[i2].y + dm_y; - out_vtx[1].x = points[i2].x - dm_x; - out_vtx[1].y = points[i2].y - dm_y; - - if (use_texture) + // Limit the inner miter offset to avoid overshooting. + const float ref_thickness = (sin_theta < 0.0f) ? thickness1 : thickness0; + const float ref_thickness_sqr = ref_thickness * ref_thickness; + const float limit_sqr = ImMax(ImMax(len_sqr0, len_sqr1), ref_thickness_sqr); + const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; + if (ref_miter_dist_sqr > limit_sqr) { - // Add indices for two triangles - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 1); // Right tri - _IdxWritePtr[3] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[4] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Left tri - _IdxWritePtr += 6; + const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); + miter_offset_x *= scale; + miter_offset_y *= scale; + } + + float bn_x = n0.x + n1.x; + float bn_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bn_x, bn_y); + + const float side_offset = ((n0.x * bn_x + n0.y * bn_y) - 1.0f) / (n0.y * bn_x - n0.x * bn_y); + const float sd_x = bn_y * side_offset; + const float sd_y = -bn_x * side_offset; + + // Bevel + if (sin_theta < 0.0f) + { + IM_APPEND_VTX(p1.x - (bn_x + sd_x) * thickness0, p1.y - (bn_y + sd_y) * thickness0, uv0, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - (bn_x - sd_x) * thickness0, p1.y - (bn_y - sd_y) * thickness0, uv0, col); // 4 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 5 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 5); + IM_APPEND_TRI(base_idx + 0, base_idx + 5, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + base_idx = next_base_idx; } else { - // Add indexes for four triangles - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); // Right tri 1 - _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Right tri 2 - _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); // Left tri 1 - _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); // Left tri 2 - _IdxWritePtr += 12; - } + IM_APPEND_VTX(p1.x + (bn_x + sd_x) * thickness1, p1.y + (bn_y + sd_y) * thickness1, uv1, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 4 + IM_APPEND_VTX(p1.x + (bn_x - sd_x) * thickness1, p1.y + (bn_y - sd_y) * thickness1, uv1, col); // 5 - idx1 = idx2; - } - - // Add vertices for each point on the line - if (use_texture) - { - // If we're using textures we only need to emit the left/right edge vertices - ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness]; - /*if (fractional_thickness != 0.0f) // Currently always zero when use_texture==false! - { - const ImVec4 tex_uvs_1 = _Data->TexUvLines[integer_thickness + 1]; - tex_uvs.x = tex_uvs.x + (tex_uvs_1.x - tex_uvs.x) * fractional_thickness; // inlined ImLerp() - tex_uvs.y = tex_uvs.y + (tex_uvs_1.y - tex_uvs.y) * fractional_thickness; - tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness; - tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness; - }*/ - ImVec2 tex_uv0(tex_uvs.x, tex_uvs.y); - ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w); - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = tex_uv0; _VtxWritePtr[0].col = col; // Left-side outer edge - _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = tex_uv1; _VtxWritePtr[1].col = col; // Right-side outer edge - _VtxWritePtr += 2; + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 4, base_idx + 2); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + base_idx = next_base_idx; } } else { - // If we're not using a texture, we need the center vertex as well - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; // Center of line - _VtxWritePtr[1].pos = temp_points[i * 2 + 0]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col_trans; // Left-side outer edge - _VtxWritePtr[2].pos = temp_points[i * 2 + 1]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col_trans; // Right-side outer edge - _VtxWritePtr += 3; - } + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; } + + point_idx++; + } + } + + // End cap + if (!closed) + { + p1 = points[points_count - 1]; + n1 = normals[points_count - 1]; + + // End cap + dir.x = n1.y; + dir.y = -n1.x; + if (flags & ImDrawFlags_CapSquare) + { + p1.x += dir.x * thickness * 0.5f; + p1.y += dir.y * thickness * 0.5f; + } + + if (!use_aa_ends) + { + IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); } else { - // [PATH 2] Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point - const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + pa.x = p1.x - dir.x * half_aa; + pa.y = p1.y - dir.y * half_aa; + pb.x = p1.x + dir.x * half_aa; + pb.y = p1.y + dir.y * half_aa; - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (!closed) - { - const int points_last = points_count - 1; - temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); - temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); - temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[points_last * 4 + 0] = points[points_last] + temp_normals[points_last] * (half_inner_thickness + AA_SIZE); - temp_points[points_last * 4 + 1] = points[points_last] + temp_normals[points_last] * (half_inner_thickness); - temp_points[points_last * 4 + 2] = points[points_last] - temp_normals[points_last] * (half_inner_thickness); - temp_points[points_last * 4 + 3] = points[points_last] - temp_normals[points_last] * (half_inner_thickness + AA_SIZE); - } + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment - for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment - { - const int i2 = (i1 + 1) == points_count ? 0 : (i1 + 1); // i2 is the second point of the line segment - const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : (idx1 + 4); // Vertex index for end of segment + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col_trans); - // Average normals - float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; - float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - float dm_out_x = dm_x * (half_inner_thickness + AA_SIZE); - float dm_out_y = dm_y * (half_inner_thickness + AA_SIZE); - float dm_in_x = dm_x * half_inner_thickness; - float dm_in_y = dm_y * half_inner_thickness; + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; - // Add temporary vertices - ImVec2* out_vtx = &temp_points[i2 * 4]; - out_vtx[0].x = points[i2].x + dm_out_x; - out_vtx[0].y = points[i2].y + dm_out_y; - out_vtx[1].x = points[i2].x + dm_in_x; - out_vtx[1].y = points[i2].y + dm_in_y; - out_vtx[2].x = points[i2].x - dm_in_x; - out_vtx[2].y = points[i2].y - dm_in_y; - out_vtx[3].x = points[i2].x - dm_out_x; - out_vtx[3].y = points[i2].y - dm_out_y; - - // Add indexes - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); - _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 1); - _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); - _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); - _IdxWritePtr[12] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[13] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[14] = (ImDrawIdx)(idx1 + 3); - _IdxWritePtr[15] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[16] = (ImDrawIdx)(idx2 + 3); _IdxWritePtr[17] = (ImDrawIdx)(idx2 + 2); - _IdxWritePtr += 18; - - idx1 = idx2; - } - - // Add vertices - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = temp_points[i * 4 + 0]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col_trans; - _VtxWritePtr[1].pos = temp_points[i * 4 + 1]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = temp_points[i * 4 + 2]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = temp_points[i * 4 + 3]; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col_trans; - _VtxWritePtr += 4; - } + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; } else { - // [PATH 4] Non texture-based, Non anti-aliased lines - const int idx_count = count * 6; - const int vtx_count = count * 4; // FIXME-OPT: Not sharing edges - PrimReserve(idx_count, vtx_count); - - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; - const ImVec2& p1 = points[i1]; - const ImVec2& p2 = points[i2]; - - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - dx *= (thickness * 0.5f); - dy *= (thickness * 0.5f); - - _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + 2); - _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx + 3); - _IdxWritePtr += 6; - _VtxCurrentIdx += 4; - } + // Connect the path. + VtxBuffer.Data[first_vtx_offset + 0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0]; + VtxBuffer.Data[first_vtx_offset + 1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 1]; } + + // Restore unused memory + const int cur_idx_offset = (int)(_IdxWritePtr - IdxBuffer.Data); + const int cur_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); + const int remaining_idx_count = IdxBuffer.Size - cur_idx_offset; + const int remaining_vtx_count = VtxBuffer.Size - cur_vtx_offset; + IM_ASSERT_PARANOID(remaining_idx_count >= 0 && remaining_vtx_count >= 0); + PrimUnreserve(remaining_idx_count, remaining_vtx_count); } // - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. -void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags) { if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; const ImVec2 uv = _Data->TexUvWhitePixel; - - if (Flags & ImDrawListFlags_AntiAliasedFill) + flags |= Flags; + if (flags & ImDrawFlags_AAFill) { - // Anti-aliased Fill - const float AA_SIZE = _FringeScale; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int idx_count = (points_count - 2)*3 + points_count * 6; - const int vtx_count = (points_count * 2); + const float half_aa = _FringeScale * 0.5f; + ImU32 col_trans = col & ~IM_COL32_A_MASK; + const bool miters_only = (flags & ImDrawFlags_JoinMiter) != 0; + const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; + + const int idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3; + const int vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS; PrimReserve(idx_count, vtx_count); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; - // Add indexes for fill - unsigned int vtx_inner_idx = _VtxCurrentIdx; - unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; - for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + ((i - 1) << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (i << 1)); - _IdxWritePtr += 3; - } - - // Compute normals - _Data->TempBuffer.reserve_discard(points_count); + // Compute normals and segment lengths + _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* temp_normals = _Data->TempBuffer.Data; + float* temp_sqr_lengths = (float*)(_Data->TempBuffer.Data + points_count); for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { const ImVec2& p0 = points[i0]; const ImVec2& p1 = points[i1]; float dx = p1.x - p0.x; float dy = p1.y - p0.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); + float d2 = dx * dx + dy * dy; + if (d2 > 0.0f) + { + const float inv_len = ImRsqrtPrecise(d2); + dx *= inv_len; + dy *= inv_len; + } temp_normals[i0].x = dy; temp_normals[i0].y = -dx; + temp_sqr_lengths[i0] = d2; } - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + unsigned int vtx_inner_idx = _VtxCurrentIdx; + ImDrawVert* inner_vtx_ptr = _VtxWritePtr; + _VtxWritePtr += points_count; + _VtxCurrentIdx += points_count; + + unsigned int prev_outer_idx = 0; // This should be the index of the last vertex, but we don't know it yet. Will need to patch once we're done. + if (miters_only) { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - float dm_x = (n0.x + n1.x) * 0.5f; - float dm_y = (n0.y + n1.y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - dm_x *= AA_SIZE * 0.5f; - dm_y *= AA_SIZE * 0.5f; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; - // Add vertices - _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner - _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer - _VtxWritePtr += 2; + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; - // Add indexes for fringes - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); - _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); - _IdxWritePtr += 6; + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + const unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + + prev_outer_idx = outer_idx; + } } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; + else + { + int bevel_count = 0; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); + float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + if (bevel) + { + // Limit inner bevel so that it is does not shoot out outside the polygon. + // (using ImMax is intentional: using min limited too much in Mikko's tests when you have short edge next to long one. + // The idea of that check is to keep that worst case overshoot no bigger than the polygon) + const float ref_thickness_sqr = half_aa * half_aa; + const float limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); + const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; + if (ref_miter_dist_sqr > limit_sqr) + { + const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); + miter_offset_x *= scale; + miter_offset_y *= scale; + } + } + + miter_offset_x *= half_aa; + miter_offset_y *= half_aa; + + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + if (bevel && bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) IM_UNLIKELY + { + // Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + // We keep track of them just in case the calculations disagree. + bevel_count++; + + IM_APPEND_VTX(p1.x + n0.x * half_aa, p1.y + n0.y * half_aa, uv, col_trans); + IM_APPEND_VTX(p1.x + n1.x * half_aa, p1.y + n1.y * half_aa, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + // Fill bevel + IM_APPEND_TRI(outer_idx, outer_idx + 1, inner_idx); + outer_idx++; + } + else + { + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + } + + prev_outer_idx = outer_idx; + } + } + + // Patch first segment to wrap around + start_idx_ptr[0] = (ImDrawIdx)prev_outer_idx; + start_idx_ptr[3] = (ImDrawIdx)prev_outer_idx; + + // Add indices for fill + for (int i = 2; i < points_count; i++) + IM_APPEND_TRI(vtx_inner_idx, vtx_inner_idx + i - 1, vtx_inner_idx + i); + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); + if (idx_used < idx_count || vtx_used < vtx_count) + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } else { // Non Anti-aliased Fill - const int idx_count = (points_count - 2)*3; + const int idx_count = (points_count - 2) * 3; const int vtx_count = points_count; PrimReserve(idx_count, vtx_count); + int base_idx = _VtxCurrentIdx; for (int i = 0; i < vtx_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr++; - } + IM_APPEND_VTX(points[i].x, points[i].y, uv, col); for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + i); - _IdxWritePtr += 3; - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; + IM_APPEND_TRI(base_idx, base_idx + i - 1, base_idx + i); } } +// Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. +IM_MSVC_RUNTIME_CHECKS_OFF +static void CalcCornerArcStepAndCount(int segment_count, int& arc_step, int& arc_step_count) +{ + arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / segment_count, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + arc_step_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step; + if ((arc_step * arc_step_count) != (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4)) // Make sure that the arc step can iterate 90 degree arc. + arc_step = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step_count; +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1169,10 +1532,17 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ // Calculate arc auto segment step size if (a_step <= 0) - a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); - - // Make sure we never do steps larger than one quarter of the circle - a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + { + // Calculate step size using the common function. No need to clamp, the step is already in range. + const int auto_seg_count = _CalcCircleAutoSegmentCount(radius); + int arc_step_count; + CalcCornerArcStepAndCount(auto_seg_count, a_step, arc_step_count); + } + else + { + // Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + } const int sample_range = ImAbs(a_max_sample - a_min_sample); const int a_next_step = a_step; @@ -1253,7 +1623,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1272,7 +1642,7 @@ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, fl // 0: East, 3: South, 6: West, 9: North, 12: East void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1282,7 +1652,7 @@ void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1296,16 +1666,25 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa // Automatic segment count if (radius <= _Data->ArcFastRadiusCutoff) - { + { const bool a_is_reverse = a_max < a_min; + // Calculate number of segments per circle. + // First we calculate the step count as usual, then match to the precomputed directions. + // This will give us the spacing between the samples (step), and how many segments to use in total for a full circle. + const int auto_seg_count = _CalcCircleAutoSegmentCount(radius); + int arc_step, arc_step_count; + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_seg_count = arc_step_count * 4; // Seg count for full circle. + // We are going to use precomputed values for mid samples. // Determine first and last sample in lookup table that belong to the arc. - const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f); - const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f); + // In first step we use the coarser segment count, and after rounding, convert back to the precomputed array index. + const float a_min_sample_f = arc_seg_count * a_min / (IM_PI * 2.0f); + const float a_max_sample_f = arc_seg_count * a_max / (IM_PI * 2.0f); - const int a_min_sample = a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f); - const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f); + const int a_min_sample = (a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f)) * arc_step; + const int a_max_sample = (a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f)) * arc_step; const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0); const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; @@ -1317,7 +1696,7 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa if (a_emit_start) _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)); if (a_mid_samples > 0) - _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0); + _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, arc_step); if (a_emit_end) _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)); } @@ -1370,7 +1749,7 @@ ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p } // Closely mimics ImBezierCubicClosestPointCasteljau() in imgui.cpp -static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float max_error_sqr, int level) { float dx = x4 - x1; float dy = y4 - y1; @@ -1378,7 +1757,7 @@ static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, fl float d3 = (x3 - x4) * dy - (y3 - y4) * dx; d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; - if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + if ((d2 + d3) * (d2 + d3) < max_error_sqr * (dx * dx + dy * dy)) { path->push_back(ImVec2(x4, y4)); } @@ -1390,16 +1769,16 @@ static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, fl float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f; float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f; - PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); - PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, max_error_sqr, level + 1); + PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, max_error_sqr, level + 1); } } -static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level) +static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float max_error_sqr, int level) { float dx = x3 - x1, dy = y3 - y1; float det = (x2 - x3) * dy - (y2 - y3) * dx; - if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy)) + if (det * det * 4.0f < max_error_sqr * (dx * dx + dy * dy)) { path->push_back(ImVec2(x3, y3)); } @@ -1408,8 +1787,8 @@ static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1 float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; - PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1); - PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1); + PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, max_error_sqr, level + 1); + PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, max_error_sqr, level + 1); } } @@ -1418,8 +1797,9 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons ImVec2 p1 = _Path.back(); if (num_segments == 0) { - IM_ASSERT(_Data->CurveTessellationTol > 0.0f); - PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated + IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); + float max_error_sqr = (_Data->CurveTessellationMaxError * _FringeScale) * (_Data->CurveTessellationMaxError * _FringeScale); + PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, max_error_sqr, 0); // Auto-tessellated } else { @@ -1434,8 +1814,9 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, ImVec2 p1 = _Path.back(); if (num_segments == 0) { - IM_ASSERT(_Data->CurveTessellationTol > 0.0f); - PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated + IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); + float max_error_sqr = (_Data->CurveTessellationMaxError * _FringeScale) * (_Data->CurveTessellationMaxError * _FringeScale); + PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, max_error_sqr, 0);// Auto-tessellated } else { @@ -1447,15 +1828,16 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { - if (rounding >= 0.5f) + const bool has_rounding = IM_HAS_ROUNDING(rounding); + if (has_rounding) { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; - rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); } - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (!has_rounding || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { PathLineTo(a); PathLineTo(ImVec2(b.x, a.y)); @@ -1464,10 +1846,10 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } else { - const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? rounding : 0.0f; - const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? rounding : 0.0f; - const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f; - const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? rounding : 0.0f; + const float rounding_tl = (flags & ImDrawFlags_RoundTopLeft) ? rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundTopRight) ? rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundBottomRight) ? rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundBottomLeft) ? rounding : 0.0f; PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); @@ -1475,71 +1857,777 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } -void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness) +void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { - if ((col & IM_COL32_A_MASK) == 0) + if ((col & IM_COL32_A_MASK) == 0) IM_UNLIKELY return; - const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; - AddPolyline(points, 2, col, thickness); + + float screen_thickness = thickness * _InvFringeScale; + if (screen_thickness < 1.0f) IM_UNLIKELY + { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } + + flags |= Flags; + ImVec2 uv0, uv1; + float fringe; + _SelectLineTexture(screen_thickness, &uv0, &uv1, &fringe, flags); + + float dir_x = p2.x - p1.x; + float dir_y = p2.y - p1.y; + const float d2 = dir_x * dir_x + dir_y * dir_y; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + dir_x *= inv_len; + dir_y *= inv_len; + const float norm_x = -dir_y; + const float norm_y = dir_x; + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + flags = (flags | ImDrawFlags_JoinMiter) & ~ImDrawFlags_AALineEnds; + + float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) + if (stroke_pos == ImDrawFlags_StrokeOutside) + thickness0 = thickness + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + thickness0 = fringe * 0.5f; + const float thickness1 = (thickness + fringe) - thickness0; + + const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offsets. + const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, uv0.y); + + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. + const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); + + if (!use_aa_ends) + PrimReserve(2*3, 4); + else + PrimReserve(6*3, 8); + + int base_idx = (int)_VtxCurrentIdx; + + float p1_x = p1.x; + float p1_y = p1.y; + float p2_x = p2.x; + float p2_y = p2.y; + if (flags & ImDrawFlags_CapSquare) + { + const float half_thickness = thickness * 0.5f; + p1_x -= dir_x * half_thickness; + p1_y -= dir_y * half_thickness; + p2_x += dir_x * half_thickness; + p2_y += dir_y * half_thickness; + } + + // Start cap + if (!use_aa_ends) + { + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(p1_x + norm_x * thickness1, p1_y + norm_y * thickness1, uv1, col); + IM_APPEND_VTX(p2_x - norm_x * thickness0, p2_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(p2_x + norm_x * thickness1, p2_y + norm_y * thickness1, uv1, col); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + } + else + { + // Start + float pa_x = p1_x - dir_x * half_aa; + float pa_y = p1_y - dir_y * half_aa; + float pb_x = p1_x + dir_x * half_aa; + float pb_y = p1_y + dir_y * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, uv1, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + + // End + pa_x = p2_x - dir_x * half_aa; + pa_y = p2_y - dir_y * half_aa; + pb_x = p2_x + dir_x * half_aa; + pb_y = p2_y + dir_y * half_aa; + + next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, uv1, col); + IM_APPEND_VTX(pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, uv1, col_trans); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + } } -void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness) +void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + { + _AddLine(ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f), col, thickness, flags); + return; + } + + _AddLine(p1, p2, col, thickness, flags); } -void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) +// AddLineH(10, 20, 5, col, 1.0f) == AddLine( {10,5.5}, {20,5.5}, col, 1.0f) +// == AddLine( {10,5}, {20,5}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +// Thickness extend inside/bottom by default. Faster than equivalent AddLine() call. +void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + { + _AddLine(ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f), col, thickness, flags); + return; + } + + float top_y = y; // Inside + if (stroke_pos == ImDrawFlags_StrokeCenter) + top_y -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + top_y -= _CalculateCenterBiasedOffset(thickness); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + top_y -= thickness; + + const bool is_truncated = IM_IS_TRUNCATED4(min_x, max_x, top_y, thickness); + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_CapSquare) == 0) + { + if (thickness < _FringeScale) IM_UNLIKELY + { + if (thickness < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, thickness * _InvFringeScale); + thickness = _FringeScale; + } + + // For pixel aligned case, use simple rectangle. + PrimReserve(6, 4); + PrimRect(ImVec2(min_x, top_y), ImVec2(max_x, top_y + thickness), col); + } + else + { + // Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + + // For generic case use line, since it needs less triangles than AA rectangle. + _AddLine(ImVec2(min_x, y), ImVec2(max_x, y), col, thickness, flags); + } +} + +// AddLineV(5, 10, 20, col, 1.0f) == AddLine( {5.5,10}, {5.5,20}, col, 1.0f) +// == AddLine( {5,10}, {5,20}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +// Thickness extend inside/right by default. Faster than equivalent AddLine() call. +void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness, ImDrawFlags flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + { + _AddLine(ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f), col, thickness, flags); + return; + } + + float left_x = x; // Inside + if (stroke_pos == ImDrawFlags_StrokeCenter) + left_x -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + left_x -= _CalculateCenterBiasedOffset(thickness); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + left_x -= thickness; + + const bool is_truncated = IM_IS_TRUNCATED4(left_x, min_y, max_y, thickness); + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_CapSquare) == 0) + { + if (thickness < _FringeScale) IM_UNLIKELY + { + if (thickness < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, thickness * _InvFringeScale); + thickness = _FringeScale; + } + + // For pixel aligned case, use simple rectangle. + PrimReserve(6, 4); + PrimRect(ImVec2(left_x, min_y), ImVec2(left_x + thickness, max_y), col); + } + else + { + // Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + + // For generic case use line, since it needs less triangles than AA rectangle. + // Drawing for bottom to top, so that the inside stroke pos expands to right. + _AddLine(ImVec2(x, max_y), ImVec2(x, min_y), col, thickness, flags); + } +} + +void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags) +{ + const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); + const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y); + const ImVec2 uv_br(tex_uvs.z, tex_uvs.w); + const ImVec2 uv_bl(tex_uvs.x, tex_uvs.w); + + const int idx_allocated = 8 * 2 * 3; + const int vtx_allocated = 8 * 4; + PrimReserve(idx_allocated, vtx_allocated); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + if (flags & ImDrawFlags_RoundTopLeft) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_min.x + r, p_min.y + r, uv_br, col); + IM_APPEND_VTX(p_min.x, p_min.y + r, uv_bl, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + + if (flags & ImDrawFlags_RoundTopRight) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_max.x - r, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_max.x, p_min.y + r, uv_bl, col); + IM_APPEND_VTX(p_max.x - r, p_min.y + r, uv_br, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + + if (flags & ImDrawFlags_RoundBottomRight) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_max.x - r, p_max.y - r, uv_br, col); + IM_APPEND_VTX(p_max.x, p_max.y - r, uv_bl, col); + IM_APPEND_VTX(p_max.x, p_max.y, uv_tl, col); + IM_APPEND_VTX(p_max.x - r, p_max.y, uv_tr, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + + if (flags & ImDrawFlags_RoundBottomLeft) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_min.x, p_max.y - r, uv_bl, col); + IM_APPEND_VTX(p_min.x + r, p_max.y - r, uv_br, col); + IM_APPEND_VTX(p_min.x + r, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_min.x, p_max.y, uv_tl, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + + const float r_tl = (flags & ImDrawFlags_RoundTopLeft) ? r : 0; + const float r_tr = (flags & ImDrawFlags_RoundTopRight) ? r : 0; + const float r_br = (flags & ImDrawFlags_RoundBottomRight) ? r : 0; + const float r_bl = (flags & ImDrawFlags_RoundBottomLeft) ? r : 0; + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + + { + // Top + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float x0 = p_min.x + r_tl; + const float x1 = p_max.x - r_tr; + IM_APPEND_VTX(x0, p_min.y, opaque_uv, col); + IM_APPEND_VTX(x1, p_min.y, opaque_uv, col); + IM_APPEND_VTX(x1, p_min.y + t, opaque_uv, col); + IM_APPEND_VTX(x0, p_min.y + t, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + { + // Left + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float y0 = p_min.y + ImMax(r_tl, t); + const float y1 = p_max.y - ImMax(r_bl, t); + IM_APPEND_VTX(p_min.x, y0, opaque_uv, col); + IM_APPEND_VTX(p_min.x + t, y0, opaque_uv, col); + IM_APPEND_VTX(p_min.x + t, y1, opaque_uv, col); + IM_APPEND_VTX(p_min.x, y1, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + { + // Right + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float y0 = p_min.y + ImMax(r_tr, t); + const float y1 = p_max.y - ImMax(r_br, t); + IM_APPEND_VTX(p_max.x - t, y0, opaque_uv, col); + IM_APPEND_VTX(p_max.x, y0, opaque_uv, col); + IM_APPEND_VTX(p_max.x, y1, opaque_uv, col); + IM_APPEND_VTX(p_max.x - t, y1, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + { + // Bottom + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float x0 = p_min.x + r_bl; + const float x1 = p_max.x - r_br; + IM_APPEND_VTX(x0, p_max.y - t, opaque_uv, col); + IM_APPEND_VTX(x1, p_max.y - t, opaque_uv, col); + IM_APPEND_VTX(x1, p_max.y, opaque_uv, col); + IM_APPEND_VTX(x0, p_max.y, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + } + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT_PARANOID(idx_used <= idx_allocated && vtx_used <= vtx_allocated); + if (idx_used < idx_allocated || vtx_used < vtx_allocated) + PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); +} + +// Draws rounded rectangle where thickness > rounding. If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. +// The stroke is positioned inside the rectangle. +void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) +{ + float screen_thickness = thickness * _InvFringeScale; + if (screen_thickness < 1.0f) IM_UNLIKELY + { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } + + flags |= Flags; + ImVec2 outer_uv, inner_uv; + float fringe; + _SelectLineTexture(screen_thickness, &outer_uv, &inner_uv, &fringe, flags); + + const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); + int arc_step, arc_step_count; + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_point_count = arc_step_count + 1; + + int vtx_count = (2 + arc_point_count) * 4 + 3; + int idx_count = (4 + arc_point_count - 1) * 4 * 3; + + PrimReserve(idx_count, vtx_count); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; + static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundTopLeft, ImDrawFlags_RoundTopRight, ImDrawFlags_RoundBottomRight, ImDrawFlags_RoundBottomLeft }; + const float half_fringe = fringe * 0.5f; + const ImVec2 corner_pos[4] = + { + ImVec2(p_min.x - half_fringe, p_min.y - half_fringe), + ImVec2(p_max.x + half_fringe, p_min.y - half_fringe), + ImVec2(p_max.x + half_fringe, p_max.y + half_fringe), + ImVec2(p_min.x - half_fringe, p_max.y + half_fringe) + }; + thickness += fringe; + rounding += half_fringe; + + const float stem_offset = ImMin(rounding, thickness); // The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. + const float ratio = stem_offset / thickness; + const ImVec2 stem_uv(outer_uv.x + (inner_uv.x - outer_uv.x) * ratio, outer_uv.y); + + // Previous vertices, copied from the last vertices. + int base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, inner_uv, col); + IM_APPEND_VTX(0, 0, stem_uv, col); + IM_APPEND_VTX(0, 0, outer_uv, col); + + for (int corner = 0; corner < 4; corner++) + { + const float outer_x = corner_pos[corner].x; + const float outer_y = corner_pos[corner].y; + const float stem_x = outer_x + corner_offset[corner].x * stem_offset; + const float stem_y = outer_y + corner_offset[corner].y * stem_offset; + const float inner_x = outer_x + corner_offset[corner].x * thickness; + const float inner_y = outer_y + corner_offset[corner].y * thickness; + const bool is_rounded = (flags & corner_flags[corner]); + + const int arc_idx = (int)_VtxCurrentIdx; + if (is_rounded) + { + const float center_x = outer_x + corner_offset[corner].x * rounding; + const float center_y = outer_y + corner_offset[corner].y * rounding; + int a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; + for (int i = 0; i < arc_point_count; i++) + { + IM_APPEND_VTX(center_x - _Data->ArcFastVtx[a].x * rounding, center_y - _Data->ArcFastVtx[a].y * rounding, outer_uv, col); + a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; + } + } + else + { + IM_APPEND_VTX(outer_x, outer_y, outer_uv, col); + } + + const int stem_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(stem_x, stem_y, stem_uv, col); + const int inner_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(inner_x, inner_y, inner_uv, col); + + // Arc + if (is_rounded) + for (int i = 0; i < arc_point_count - 1; i++) + { + IM_APPEND_TRI(stem_idx, arc_idx + i, arc_idx + i + 1); + } + + // Connect with previous + IM_APPEND_TRI(base_idx + 0, arc_idx, stem_idx); + IM_APPEND_TRI(base_idx + 0, stem_idx, base_idx + 1); + IM_APPEND_TRI(base_idx + 1, stem_idx, inner_idx); + IM_APPEND_TRI(base_idx + 1, inner_idx, base_idx + 2); + + base_idx = stem_idx - 1; + } + + // Close + start_vtx_ptr[0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0]; + start_vtx_ptr[1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 1]; + start_vtx_ptr[2] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 2]; + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT_PARANOID(idx_used <= idx_count && vtx_used <= vtx_count); + if (idx_used < idx_count || vtx_used < vtx_count) + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } // p_min = upper-left, p_max = lower-right -// Note we don't render 1 pixels sized rectangles properly. void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { // If this assert triggers on legacy code: // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values. + // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. // If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! - // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. + // - Hard coded support for ~0 == ImDrawFlags_RoundAll. // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. - // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. + // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. // See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values if ((col & IM_COL32_A_MASK) == 0) return; - if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + { + if (flags & ImDrawFlags_AALines) + PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); + else + PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.49f, p_max.y - 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeCenter | (flags & ~ImDrawFlags_StrokeMask_)); + return; + } + + const bool has_rounding = IM_HAS_ROUNDING(rounding); + + // Calculate the outer boundary of the rectangle based on the stroke position. + ImVec2 outer_min = p_min; + ImVec2 outer_max = p_max; + float outer_rounding = rounding; + + float offset = 0.0f; // Inside + if (stroke_pos == ImDrawFlags_StrokeCenter) + offset = thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + offset = _CalculateCenterBiasedOffset(thickness); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + offset = thickness; + outer_min.x -= offset; + outer_min.y -= offset; + outer_max.x += offset; + outer_max.y += offset; + outer_rounding += has_rounding ? offset : 0.0f; + + const float width = outer_max.x - outer_min.x; + const float height = outer_max.y - outer_min.y; + const float half_min_dim = ImMin(width, height) * 0.5f; + if (half_min_dim <= thickness) IM_UNLIKELY + { + // Rectangle has collapsed into filled rectangle. + AddRectFilled(outer_min, outer_max, col, outer_rounding, flags); + return; + } + + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; + if (has_rounding) + { + // Constrain rounding to rect dimensions + outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); + } + + const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); //-V501 + if (_FringeScaleIsInteger && is_truncated) + { + int s_rounding = (int)(outer_rounding * _InvFringeScale); + int s_thickness = (int)(thickness * _InvFringeScale); + if (s_thickness <= 0) + return; + + if (s_rounding <= 0 || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) + { + // Pixel aligned non-rounded rect. + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + const float t = (float)s_thickness * _FringeScale; + + PrimReserve(8*3, 8); + const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(outer_min.x, outer_min.y, opaque_uv, col); + IM_APPEND_VTX(outer_min.x + t, outer_min.y + t, opaque_uv, col); + IM_APPEND_VTX(outer_max.x, outer_min.y, opaque_uv, col); + IM_APPEND_VTX(outer_max.x - t, outer_min.y + t, opaque_uv, col); + IM_APPEND_VTX(outer_max.x, outer_max.y, opaque_uv, col); + IM_APPEND_VTX(outer_max.x - t, outer_max.y - t, opaque_uv, col); + IM_APPEND_VTX(outer_min.x, outer_max.y, opaque_uv, col); + IM_APPEND_VTX(outer_min.x + t, outer_max.y - t, opaque_uv, col); + + IM_APPEND_TRI(idx + 0, idx + 3, idx + 1); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + + IM_APPEND_TRI(idx + 2, idx + 5, idx + 3); + IM_APPEND_TRI(idx + 2, idx + 4, idx + 5); + + IM_APPEND_TRI(idx + 4, idx + 7, idx + 5); + IM_APPEND_TRI(idx + 4, idx + 6, idx + 7); + + IM_APPEND_TRI(idx + 6, idx + 1, idx + 7); + IM_APPEND_TRI(idx + 6, idx + 0, idx + 1); + + return; + } + // Textured corners are baked with AA, do not use them if no-AA is requested. + const bool allow_tex_corners = (flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_); + if (allow_tex_corners && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + { + // Pixel aligned rect with round corners rendered using baked textures. + IM_ASSERT_PARANOID(s_thickness > 0 && s_rounding > 0); + const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. + const int idx = (s_thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + s_rounding - 1; + IM_ASSERT_PARANOID((_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) == 0); + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); + _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, _Data->TexUvCorners[idx], flags); + return; + } + } + + if (!has_rounding || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) + { + const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; + _AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeInside, half_min_dim); + _Path.Size = 0; + } else - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, thickness, ImDrawFlags_Closed); + { + // Switch to specialized rendering a bit early to avoid overdraw visible in transparent shapes. + if (thickness + (0.75f * _FringeScale) > outer_rounding) + { + // Special case rendering to avoid rendering artifacts at the corners. + // If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. + _AddRectTinyRounding(outer_min, outer_max, col, outer_rounding, thickness, flags); + return; + } + const float rounding_tl = (flags & ImDrawFlags_RoundTopLeft) ? outer_rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundTopRight) ? outer_rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundBottomRight) ? outer_rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundBottomLeft) ? outer_rounding : 0.0f; + PathArcToFast(ImVec2(outer_min.x + rounding_tl, outer_min.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); + + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_), half_min_dim); + _Path.Size = 0; + } +} + +void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags) +{ + // 9-slice with corner mirroring + const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); + const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y); + const ImVec2 uv_br(tex_uvs.z, tex_uvs.w); + const ImVec2 uv_bl(tex_uvs.x, tex_uvs.w); + + PrimReserve(9 * 2 * 3, 4 * 4); + + const float r_tl = (flags & ImDrawFlags_RoundTopLeft) ? r : 0.0f; + const float r_tr = (flags & ImDrawFlags_RoundTopRight) ? r : 0.0f; + const float r_br = (flags & ImDrawFlags_RoundBottomRight) ? r : 0.0f; + const float r_bl = (flags & ImDrawFlags_RoundBottomLeft) ? r : 0.0f; + + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r_tl, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x - r_tr, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_min.y, uv_tl, col); + + IM_APPEND_VTX(p_min.x, p_min.y + r_tl, uv_bl, col); + IM_APPEND_VTX(p_min.x + r_tl, p_min.y + r_tl, uv_br, col); + IM_APPEND_VTX(p_max.x - r_tr, p_min.y + r_tr, uv_br, col); + IM_APPEND_VTX(p_max.x, p_min.y + r_tr, uv_bl, col); + + IM_APPEND_VTX(p_min.x, p_max.y - r_bl, uv_bl, col); + IM_APPEND_VTX(p_min.x + r_bl, p_max.y - r_bl, uv_br, col); + IM_APPEND_VTX(p_max.x - r_br, p_max.y - r_br, uv_br, col); + IM_APPEND_VTX(p_max.x, p_max.y - r_br, uv_bl, col); + + IM_APPEND_VTX(p_min.x, p_max.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r_bl, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_max.x - r_br, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_max.y, uv_tl, col); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + IM_APPEND_TRI(idx + 4, idx + 0, idx + 1); + IM_APPEND_TRI(idx + 4, idx + 1, idx + 5); + idx++; + } + idx++; + } } void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + + float width = p_max.x - p_min.x; + float height = p_max.y - p_min.y; + const float min_dim = ImMin(width, height); + flags |= Flags; + + if ((flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy && rounding < 0.5f) IM_UNLIKELY { PrimReserve(6, 4); PrimRect(p_min, p_max, col); + return; + } + + // The rect is smaller than pixel in one dimension. + if (min_dim < _FringeScale) IM_UNLIKELY + { + if (min_dim < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY + return; + if (width < _FringeScale) + { + col = ImAlphaMultiply(col, width * _InvFringeScale); + width = _FringeScale; + } + if (height < _FringeScale) + { + col = ImAlphaMultiply(col, height * _InvFringeScale); + height = _FringeScale; + } + const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_min.y + height) }; + AddConvexPolyFilled(points, 4, col, ImDrawFlags_JoinMiter); + return; + } + + // When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. + const bool has_rounding = (flags & ImDrawFlags_RoundMask_) != ImDrawFlags_RoundNone && IM_HAS_ROUNDING(rounding); + const bool is_truncated = IM_IS_TRUNCATED4(p_min.x, p_min.y, p_max.x, p_max.y) && IM_IS_TRUNCATED(rounding); + + if (_FringeScaleIsInteger && is_truncated) + { + // Fast path for the non-AA non-rounded rect. + if (!has_rounding) + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + return; + } + + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; + + rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); + + const int s_rounding = (int)(rounding * _InvFringeScale); + if (s_rounding <= 0) + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + return; + } + // Textured corners are baked with AA, do not use them if no-AA is requested. + const bool allow_tex_corners = (flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_); + if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + { + IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); + // This is matching the baking calculations. The rounding clamping above makes sure that there's enough space for the extra pixel. + const int corner_size = s_rounding + 1; + // Check that the baked corners do not overlap (this could be made more accurate if tested for x and y separately). + if ((float)corner_size * 2.0f * _FringeScale < min_dim) + { + const int idx = (s_rounding - 1); + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); + _AddRectFilledBaked(p_min, p_max, col, (float)corner_size * _FringeScale, _Data->TexUvCorners[idx], flags); + return; + } + } + } + + if (!has_rounding) + { + const ImVec2 points[4] = { p_min, ImVec2(p_max.x, p_min.y), p_max, ImVec2(p_min.x, p_max.y) }; + AddConvexPolyFilled(points, 4, col, ImDrawFlags_JoinMiter); } else { PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_JoinMiter); } } // p_min = upper-left, p_max = lower-right +// Note: this does not support anti-aliasing when provided non-integer vertices. void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) { if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) @@ -1555,16 +2643,17 @@ void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_ma PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left); } -void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness) +void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathLineTo(p4); - PathStroke(col, thickness, ImDrawFlags_Closed); + //flags |= Flags; // Not needed. + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different + + const ImVec2 points[4] = { p1, p2, p3, p4 }; + _AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed, FLT_MAX); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -1572,22 +2661,21 @@ void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathLineTo(p4); - PathFillConvex(col); + const ImVec2 points[4] = { p1, p2, p3, p4 }; + AddConvexPolyFilled(points, 4, col); } -void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness) +void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathStroke(col, thickness, ImDrawFlags_Closed); + //flags |= Flags; // Not needed. + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different + + const ImVec2 points[3] = { p1, p2, p3 }; + _AddPolyline(points, 3, col, thickness, flags | ImDrawFlags_Closed, FLT_MAX); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -1595,50 +2683,61 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathFillConvex(col); + const ImVec2 points[3] = { p1, p2, p3 }; + AddConvexPolyFilled(points, 3, col); } -void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +static float ApproxApothem(int num_segments) { - if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) + // Calculates approximation (within 0.001%) distance from the center of the circumscribed circle to the edge of the polygon, cos(pi / n) + IM_ASSERT_PARANOID(num_segments >= 3); + const float u = 1.0f / num_segments; + return ((4.058712f * u * u) - 4.934802f) * u * u + 1.0f; +} + +void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) +{ + if ((col & IM_COL32_A_MASK) == 0) return; - if (num_segments <= 0) + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + { + radius -= 0.5f; + stroke_pos = ImDrawFlags_StrokeCenter; + } + radius = ImMax(0.01f, radius); + + float outer_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeCenter) + outer_radius = radius + thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + outer_radius = radius + _CalculateCenterBiasedOffset(thickness); + else if (stroke_pos == ImDrawFlags_StrokeInside) + outer_radius = radius; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + outer_radius = radius + thickness; + + if (outer_radius <= thickness) IM_UNLIKELY + { + // The circle has collapsed into a filled circle. + AddCircleFilled(center, outer_radius, col, num_segments); + return; + } + + if (num_segments <= 0 && outer_radius <= _Data->ArcFastRadiusCutoff) { // Use arc with automatic segment count - _PathArcToFastEx(center, radius - 0.5f, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(outer_radius); // Use outer_radius for segment count to be consistent with rounded rect. + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); _Path.Size--; } else { - // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) - num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); - - // Because we are filling a closed shape we remove 1 from the count of segments/points - const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - } - - PathStroke(col, thickness, ImDrawFlags_Closed); -} - -void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) -{ - if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) - return; - - if (num_segments <= 0) - { - // Use arc with automatic segment count - _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); - _Path.Size--; - } - else - { - // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + // Explicit segment count or above fast arc cutoff (still clamp to avoid drawing insanely tessellated shapes) + if (num_segments <= 0) + num_segments = _CalcCircleAutoSegmentCount(outer_radius); num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); // Because we are filling a closed shape we remove 1 from the count of segments/points @@ -1646,19 +2745,98 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - PathFillConvex(col); + if (_Path.Size >= 3) + { + // Calculate distance to the edge of the polygon describing the circle. + // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + const float unit_apothem = ApproxApothem(_Path.Size); + const float edge_dist = radius * unit_apothem; + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); + } + _Path.Size = 0; +} + +void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + // Minimum radius, before the edge-AA will overlap. + const float min_radius = _FringeScale * 1.41421356f * 0.5f; + if (radius < min_radius) IM_UNLIKELY + { + if (radius < (1.0f / 255.0f) * min_radius) + return; + col = ImAlphaMultiply(col, radius / min_radius); + radius = min_radius; + } + + if (num_segments <= 0 && radius <= _Data->ArcFastRadiusCutoff) + { + // Use arc with automatic segment count + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + _Path.Size--; + } + else + { + // Explicit segment count or above cutoff (still clamp to avoid drawing insanely tessellated shapes) + if (num_segments <= 0) + num_segments = _CalcCircleAutoSegmentCount(radius); + num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); + + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + } + + PathFillConvex(col, ImDrawFlags_JoinMiter); } // Guaranteed to honor 'num_segments' -void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; - // Because we are filling a closed shape we remove 1 from the count of segments/points + flags |= Flags; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + { + radius -= 0.5f; + stroke_pos = ImDrawFlags_StrokeCenter; + } + radius = ImMax(0.01f, radius); + + // Calculate distance to the edge of the polygon describing the circle. + // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + const float unit_apothem = ApproxApothem(num_segments); + const float edge_dist = radius * unit_apothem; + + // Check overestimate first before testing details. + if (radius < thickness * 2.0f) + { + float outer_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeCenter) + outer_radius = radius + thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + outer_radius = radius + _CalculateCenterBiasedOffset(thickness); + else if (stroke_pos == ImDrawFlags_StrokeInside) + outer_radius = radius; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + outer_radius = radius + thickness; + if (outer_radius * unit_apothem <= thickness) IM_UNLIKELY + { + // The polygon has collapsed into a filled polygon. + AddNgonFilled(center, outer_radius, col, num_segments); + return; + } + } + + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed); + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); + _Path.Size = 0; } // Guaranteed to honor 'num_segments' @@ -1667,25 +2845,48 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + const float diameter = radius * 2.0f; + if (diameter < _FringeScale) IM_UNLIKELY + { + if (diameter < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY + return; + col = ImAlphaMultiply(col, diameter * _InvFringeScale); + radius = _FringeScale * 0.5f; + } + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_JoinMiter); } // Ellipse -void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness) +void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + stroke_pos = ImDrawFlags_StrokeCenter; + + ImVec2 rad = radius; + rad.x = ImMax(0.01f, rad.x); + rad.y = ImMax(0.01f, rad.y); + if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(rad.x, rad.y)); // A bit pessimistic, maybe there's a better computation to do here. + + _Path.reserve(_Path.Size + (num_segments + 1)); // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed); + PathEllipticalArcTo(center, rad, rot, 0.0f, a_max, num_segments - 1); + + // Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. + const float max_inner_offset = ImMin(rad.x, rad.y); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, max_inner_offset); + _Path.Size = 0; } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) @@ -1693,6 +2894,28 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im if ((col & IM_COL32_A_MASK) == 0) return; + ImVec2 rad = radius; + float diameter_x = rad.x * 2.0f; + float diameter_y = rad.y * 2.0f; + const float min_dim = ImMin(diameter_x, diameter_y); + + // The ellipse is smaller than pixel in one dimension. + if (min_dim < _FringeScale) IM_UNLIKELY + { + if (min_dim < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY + return; + if (diameter_x < _FringeScale) + { + col = ImAlphaMultiply(col, diameter_x * _InvFringeScale); + rad.x = _FringeScale * 0.5f; + } + if (diameter_y < _FringeScale) + { + col = ImAlphaMultiply(col, diameter_y * _InvFringeScale); + rad.y = _FringeScale * 0.5f; + } + } + if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. @@ -1703,25 +2926,25 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im } // Cubic Bezier takes 4 controls points -void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments) +void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); - PathStroke(col, thickness); + PathStroke(col, thickness, flags); } // Quadratic Bezier takes 3 controls points -void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments) +void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); - PathStroke(col, thickness); + PathStroke(col, thickness, flags); } void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) @@ -1794,10 +3017,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons return; IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect(). - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (rounding < 0.5f || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; @@ -1809,7 +3032,7 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_JoinMiter); int vert_end_idx = VtxBuffer.Size; ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); @@ -2046,7 +3269,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou const ImVec2 uv = _Data->TexUvWhitePixel; ImTriangulator triangulator; unsigned int triangle[3]; - if (Flags & ImDrawListFlags_AntiAliasedFill) + if (Flags & ImDrawFlags_AAFill) { // Anti-aliased Fill const float AA_SIZE = _FringeScale; @@ -2303,7 +3526,7 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector // May trigger for you if you are using PrimXXX functions incorrectly. IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); - if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset)) + if (!(draw_list->Flags & ImDrawFlags_UseVtxOffset)) IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) @@ -3116,7 +4339,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } if (font_cfg->FontLoader != NULL) { - IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->FontSrcGetGlyphIndexFromCodepoint != NULL && font_cfg->FontLoader->FontBakedLoadGlyph != NULL); IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. } // | Target w/ Implicit RefSize | Target w/ Explicit RefSize | @@ -3170,11 +4393,15 @@ const char* ImGui_GetDefaultCompressedFontDataProggyForever(int* out_size); // This duplicates some of the logic in UpdateFontsNewFrame() which is a bit chicken-and-eggy/tricky to extract due to variety of codepaths and possible initialization ordering. static float GetExpectedContextFontSize(ImGuiContext* ctx) { - return ((ctx->Style.FontSizeBase > 0.0f) ? ctx->Style.FontSizeBase : 13.0f) * ctx->Style.FontScaleMain * ctx->Style.FontScaleDpi; + float fb_scale = (ctx->IO.DisplayFramebufferScale.x != 0.0f) ? ctx->IO.DisplayFramebufferScale.x : 1.0f; + if (ctx->IO.DisplayFramebufferScale.x == 1.0f && ctx->IO.DisplaySize.x <= 0.0f && ctx->IO.ConfigMacOSXBehaviors) + fb_scale = 2.0f; // If called before backend had a chance to init DisplayFramebufferScale. default to expecting Retina. + float scale = ctx->Style.FontScaleMain * ctx->Style.FontScaleDpi * fb_scale; + return ((ctx->Style.FontSizeBase > 0.0f) ? ctx->Style.FontSizeBase : 13.0f) * scale; } // Legacy function with heuristic to select Pixel or Vector font. -// The selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold at the time of adding the default font. +// The selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi * io.DisplayFramebufferScale) reaching a small threshold at the time of adding the default font. // Prefer calling AddFontDefaultVector() or AddFontDefaultBitmap() based on your own logic. ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg) { @@ -3627,6 +4854,196 @@ static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } + +#define IM_DRAWLIST_CORNER_POINTS_MAX (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4 + 3) // Max number of points in corner profiles + +static int InitCornerGeometry(float cx, float cy, float rounding, float circle_segment_max_error, ImVec2* line_normals, float* line_distances) +{ + // This tries to quite faithfully replicate how we render the corners using tessellation. + // Older code sampled a circle SDF directly, but it had clearly visible discrepancy between the baked corners + // and tessellated corners due to circle_segment_max_error. + + const int auto_seg_count = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(rounding, circle_segment_max_error); + int arc_step, arc_step_count; + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_point_count = arc_step_count + 1; + + // Build arc of points, with straight segments at each end. + // . [n-2]..[n-1] + // .` + // . + // [1] x (cx,cy) + // : + // [0] + ImVec2 points[IM_DRAWLIST_CORNER_POINTS_MAX]; + int num_points = 0; + + points[num_points++] = ImVec2(cx - rounding, cy + 1.0f); + + float da = ((float)arc_step / (float)IM_DRAWLIST_ARCFAST_TABLE_SIZE) * IM_PI * 2.0f; + float a = IM_PI; + for (int i = 0; i < arc_point_count; i++) + { + float dx = cosf(a); + float dy = sinf(a); + points[num_points++] = ImVec2(cx + dx * rounding, cy + dy * rounding); + a += da; + } + + points[num_points++] = ImVec2(cx + 1.0f, cy - rounding); + + // Turn segments to half spaces. + for (int i = 0; i < num_points - 1; i++) + { + const ImVec2 diff = points[i + 1] - points[i]; + const float d2 = diff.x * diff.x + diff.y * diff.y; + const float inv_d = ImRsqrtPrecise(d2); + line_normals[i].x = diff.y * inv_d; // Normal points outwards + line_normals[i].y = -diff.x * inv_d; + line_distances[i] = -ImDot(points[i], line_normals[i]); + } + + return num_points - 1; +} + +// Returns distance to convex shape limited by lines. +static float SampleConvex(float x, float y, const ImVec2* line_normals, const float* line_distances, int num_lines) +{ + float result = -FLT_MAX; + for (int i = 0; i < num_lines; i++) + { + const float d = line_normals[i].x * x + line_normals[i].y * y + line_distances[i]; + if (d > result) + result = d; + } + return result; +} + +static ImU8 SampleCornerFillOrStroke(float x, float y, float thickness, const ImVec2* line_normals, const float* line_distances, int num_lines) +{ + const float d = SampleConvex(x, y, line_normals, line_distances, num_lines); + const float aa_width = 1.0f; + if (thickness == 0.0f) + { + // Fill + return (ImU8)(ImClamp(1.0f - (d + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); + } + else + { + // Stroke + const float outer = 1.0f - (d + aa_width * 0.5f) / aa_width; + const float inner = (d + thickness + aa_width * 0.5f) / aa_width; + return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); + } +} + +#if 0 // Set to 1 to visualize coverage of textured corners +#define DEBUG_TEX_CORNER_U8(_LX, _H) ((_LX & 1) ? 255 : 0) +#define DEBUG_TEX_CORNER_U32(_LX, _H) IM_COL32((_LX & 1) ? 255: 0, 0, 0, (_LX & 1) ? 200 : 0) +#else +#define DEBUG_TEX_CORNER_U8(_LX, _H) 0xFF +#define DEBUG_TEX_CORNER_U32(_LX, _H) 0xFFFFFFFF +#endif + +static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) +{ + if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) + return; + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // FIXME-TEXCORNERS: Ideally we should pick up the max error from the styles, but it does not seem be to set by the time we build the data the first time. + float circle_segment_max_error = 0.30f; // Default matching ImGuiStyle::CircleTessellationMaxError. + // if (atlas->OwnerContext) + // circle_segment_max_error = atlas->OwnerContext->DrawListSharedData.CircleSegmentMaxError; + + // Calculate size and pack + ImFontAtlasRect r; + bool add_and_draw = atlas->GetCustomRect(builder->PackIdCornersTexData, &r) == false; + if (add_and_draw) + { + // (thickness 0 for filled, thickness >0 for strokes) + ImVec2i pack_size(0, 0); + for (int thickness = 0; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) + { + const bool is_filled = (thickness == 0); + int row_width = 0; + int row_height = 0; + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) + { + const int rounding = n + 1; + const int size = (is_filled ? (rounding + 1) : ImMax(rounding, thickness)); + row_width += size + 2; + row_height = ImMax(row_height, size + 2); + } + pack_size.x = ImMax(pack_size.x, row_width); + pack_size.y += row_height; + } + builder->PackIdCornersTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdCornersTexData != ImFontAtlasRectId_Invalid); + } + + // Render + int y = r.y; + ImVec2 line_normals[IM_DRAWLIST_CORNER_POINTS_MAX]; + float line_distances[IM_DRAWLIST_CORNER_POINTS_MAX]; + for (int thickness = 0; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) + { + const bool is_filled = (thickness == 0); + int x = r.x; + int row_height = 0; + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) + { + // Filled: Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. + const int rounding = n + 1; + const int size = (is_filled ? (rounding + 1) : ImMax(rounding, thickness)); + const int w = size + 2; + const int h = size + 2; + + // Corner circle + const float cx = 1.0f + (float)(n + 1); + const float cy = 1.0f + (float)(n + 1); + IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds + const int num_lines = InitCornerGeometry(cx, cy, (float)rounding, circle_segment_max_error, line_normals, line_distances); + + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < w; lx++) + write_ptr[lx] = SampleCornerFillOrStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); + write_ptr += pitch; + } + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < w; lx++) + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerFillOrStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); + write_ptr += pitch; + } + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(x + 1 + size), (float)(y + 1 + size)) * atlas->TexUvScale; + builder->TexUvCorners[n + thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + + x += w; + row_height = ImMax(row_height, h); + } + y += row_height; + } +} + static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) @@ -3640,54 +5057,116 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; if (add_and_draw) { - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT); builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); } - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - const int y = n; - const int line_width = n; - const int pad_left = (r.w - line_width) / 2; - const int pad_right = r.w - (pad_left + line_width); - IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - - // Write each slice - if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + // FIXME: Lines 0 to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH(4) are currently never used and could be ditched, saving DETAILED_WIDTH*WIDTH_MAX + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + } - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 0.5f), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 0.5f), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + float inv_line_width = (line_width > 0) ? (1.0f / (float)line_width) : 0.0f; + builder->TexUvLines[n] = ImVec4(uv0.x, uv1.x, half_v, inv_line_width); } - else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + } + + { + // Calculate super sampled line textures for smaller line widths to make transition between sizes smoother. + + // Calculate ramp used for all the textures. + ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) + ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0f); + + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT; n++) { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + const int y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n; + // For integer thickness lines the one pixel line texture looks like this: + // [ 0 | 1 | 0 ] + // :...........: + // Since the texture samples are at the center of the texel, we only use range above. + // The result after bilinear filtering is a triangle across the used uv range. + // + // To super 2x sample that signal, we end up with following texture: + // [ 0 | .5 | 1 | .5 | 0 ] + // :.......................: + // One might think that there should be 2 opaque pixels in the middle section, but no since we're super sampling the triangle signal. + const int line_width = 1 + n; + IM_ASSERT(IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + line_width + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + int idx = 0; + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) + write_ptr[idx++] = ramp[i]; + for (int i = 0; i < line_width; i++) + write_ptr[idx++] = 0xFF; + for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) + write_ptr[idx++] = ramp[i]; + while (idx < r.w) + write_ptr[idx++] = 0x0; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + int idx = 0; + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) + write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); + for (int i = 0; i < line_width; i++) + write_ptr[idx++] = IM_COL32(255, 255, 255, 255); + for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) + write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); + while (idx < r.w) + write_ptr[idx++] = IM_COL32(255, 255, 255, 0); + } - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + 0.5f), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x - 0.5f + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + float inv_line_width = 1.0f / (1.0f + (float)n / (float)IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT); + builder->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, uv1.x, half_v, inv_line_width); } - - // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; - float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } @@ -4061,7 +5540,8 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) if (shared_data->FontAtlas == atlas) { shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; - shared_data->TexUvLines = atlas->TexUvLines; + shared_data->TexUvLines = atlas->Builder->TexUvLines; + shared_data->TexUvCorners = atlas->Builder->TexUvCorners; } } @@ -4080,6 +5560,7 @@ static void ImFontAtlasBuildUpdateTexData(ImFontAtlas* atlas) { ImFontAtlasBuildUpdateTexDataBasic(atlas); ImFontAtlasBuildUpdateTexDataLines(atlas); + ImFontAtlasBuildUpdateTexDataCorners(atlas); } // Create a new texture, discard previous one @@ -4551,11 +6032,46 @@ static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); } -static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +// Remap a codepoint to another codepoint +void ImFont::AddRemapCodepoint(ImWchar from_codepoint, ImWchar to_codepoint) +{ + ImU32 data = (ImU32)to_codepoint; + IM_ASSERT((data & (1u << 31)) == 0); + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)data); +} + +static const int IMFONT_REMAPVALUE_GLYPHIDX_SHIFT = 0; +static const int IMFONT_REMAPVALUE_GLYPHIDX_MASK = 0xFFFF; +static const int IMFONT_REMAPVALUE_FONTSRCIDX_SHIFT = 16; +static const int IMFONT_REMAPVALUE_FONTSRCIDX_MASK = 0xFF0000; + +// Remap a codepoint to a raw glyph index inside the font. Allows accessing glyphs which are not mapped to codepoints. (#9559) +// font_src_idx = index in font->Sources[], or -1 for last added source. +// glyph_idx = index in TTF or OTF file. +void ImFont::AddRemapCodepointToGlyphIndex(ImWchar from_codepoint, int font_src_idx, int glyph_idx) +{ + if (font_src_idx == -1) + font_src_idx = Sources.Size - 1; + IM_ASSERT(font_src_idx >= 0 && font_src_idx < Sources.Size && ((font_src_idx << IMFONT_REMAPVALUE_FONTSRCIDX_SHIFT) & ~IMFONT_REMAPVALUE_FONTSRCIDX_MASK) == 0); + IM_ASSERT(glyph_idx >= 0 && ((glyph_idx << IMFONT_REMAPVALUE_GLYPHIDX_SHIFT) & ~IMFONT_REMAPVALUE_GLYPHIDX_MASK) == 0); + ImU32 data = ((ImU32)glyph_idx << IMFONT_REMAPVALUE_GLYPHIDX_SHIFT) | (font_src_idx << IMFONT_REMAPVALUE_FONTSRCIDX_SHIFT) | (1u << 31); + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)data); +} + +static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar codepoint, ImWchar* out_codepoint, int* out_font_src_idx, int* out_glyph_idx) { IM_UNUSED(atlas); - if (font->RemapPairs.Data.Size != 0) - *c = (ImWchar)font->RemapPairs.GetInt((ImGuiID)*c, (int)*c); + if (font->RemapPairs.Data.Size == 0) + return; + ImU32 data = (ImU32)font->RemapPairs.GetInt((ImGuiID)codepoint, (int)codepoint); + if (data & (1u << 31)) + { + *out_codepoint = (ImWchar)-1; + *out_font_src_idx = (data & IMFONT_REMAPVALUE_FONTSRCIDX_MASK) >> IMFONT_REMAPVALUE_FONTSRCIDX_SHIFT; + *out_glyph_idx = (data & IMFONT_REMAPVALUE_GLYPHIDX_MASK) >> IMFONT_REMAPVALUE_GLYPHIDX_SHIFT; + return; + } + *out_codepoint = (ImWchar)data; } static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint, float* only_load_advance_x) @@ -4572,7 +6088,8 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep // User remapping hooks ImWchar src_codepoint = codepoint; - ImFontAtlas_FontHookRemapCodepoint(atlas, font, &codepoint); + int src_font_idx = -1, src_glyph_idx = -1; + ImFontAtlas_FontHookRemapCodepoint(atlas, font, codepoint, &src_codepoint, &src_font_idx, &src_glyph_idx); //char utf8_buf[5]; //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); @@ -4585,35 +6102,47 @@ static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codep // Call backend char* loader_user_data_p = (char*)baked->FontLoaderDatas; - int src_n = 0; - for (ImFontConfig* src : font->Sources) + char* loader_user_data_p_next = NULL; + for (int src_n = 0; src_n < font->Sources.Size; src_n++, loader_user_data_p = loader_user_data_p_next) { + ImFontConfig* src = font->Sources[src_n]; const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; - if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) + loader_user_data_p_next = loader_user_data_p + loader->FontBakedSrcLoaderDataSize; + + int glyph_index; + if (src_codepoint != (ImWchar)-1) { - if (only_load_advance_x == NULL) - { - ImFontGlyph glyph_buf; - if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf, NULL)) - { - // FIXME: Add hooks for e.g. #7962 - glyph_buf.Codepoint = src_codepoint; - glyph_buf.SourceIdx = src_n; - return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); - } - } - else - { - // Special mode but only loading glyphs metrics. Will rasterize and pack later. - if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, NULL, only_load_advance_x)) - { - ImFontAtlasBakedAddFontGlyphAdvancedX(atlas, baked, src, codepoint, *only_load_advance_x); - return NULL; - } - } + if (src->GlyphExcludeRanges && !ImFontAtlasBuildAcceptCodepointForSource(src, src_codepoint)) + continue; + glyph_index = loader->FontSrcGetGlyphIndexFromCodepoint(atlas, src, src_codepoint); + } + else + { + if (src_n != src_font_idx) + continue; + glyph_index = src_glyph_idx; + } + if (glyph_index == 0) + continue; + + if (only_load_advance_x == NULL) + { + ImFontGlyph glyph_buf; + glyph_buf.Codepoint = codepoint; + if (!loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, glyph_index, &glyph_buf, NULL)) + continue; + // FIXME: Add hooks for e.g. #7962 + glyph_buf.SourceIdx = src_n; + return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); + } + else + { + // Special mode but only loading glyphs metrics. Will rasterize and pack later. + if (!loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, glyph_index, NULL, only_load_advance_x)) + continue; + ImFontAtlasBakedAddFontGlyphAdvancedX(atlas, baked, src, codepoint, *only_load_advance_x); + return NULL; } - loader_user_data_p += loader->FontBakedSrcLoaderDataSize; - src_n++; } // Lazily load fallback glyph @@ -4697,10 +6226,8 @@ struct ImGui_ImplStbTrueType_FontSrcData float ScaleFactor; }; -static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas*, ImFontConfig* src) { - IM_UNUSED(atlas); - ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); IM_ASSERT(src->FontLoaderData == NULL); @@ -4732,29 +6259,27 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* return true; } -static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas*, ImFontConfig* src) { - IM_UNUSED(atlas); ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; IM_DELETE(bd_font_data); src->FontLoaderData = NULL; } -static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +static int ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint(ImFontAtlas*, ImFontConfig* src, ImWchar codepoint) { - IM_UNUSED(atlas); - ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; - IM_ASSERT(bd_font_data != NULL); - - int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); - return glyph_index != 0; + return stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); } -static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas*, ImFontConfig* src, int glyph_index) { - IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + return glyph_index > 0 && glyph_index < bd_font_data->FontInfo.numGlyphs; +} +static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas*, ImFontConfig* src, ImFontBaked* baked, void*) +{ ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; if (src->MergeMode == false) { @@ -4765,18 +6290,17 @@ static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); baked->Descent = ImFloor(unscaled_descent * scale_for_layout); + // FIXME: round to rasterizer density? (#9348) } return true; } -static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) +static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, int glyph_index, ImFontGlyph* out_glyph, float* out_advance_x) { // Search for first font which has the glyph ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; IM_ASSERT(bd_font_data); - int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); - if (glyph_index == 0) - return false; + IM_ASSERT(glyph_index != 0); // Fonts unit to pixels int oversample_h, oversample_v; @@ -4801,7 +6325,6 @@ static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontC } // Prepare glyph - out_glyph->Codepoint = codepoint; out_glyph->AdvanceX = advance * scale_for_layout; // Pack and retrieve position inside texture atlas @@ -4863,6 +6386,7 @@ const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() loader.Name = "stb_truetype"; loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + loader.FontSrcGetGlyphIndexFromCodepoint = ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint; loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; loader.FontBakedDestroy = NULL; @@ -5336,11 +6860,6 @@ void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) -{ - RemapPairs.SetInt((ImGuiID)from_codepoint, (int)to_codepoint); -} - // Find glyph, load if necessary, return fallback if missing ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) { @@ -5390,11 +6909,19 @@ bool ImFontBaked::IsGlyphLoaded(ImWchar c) bool ImFont::IsGlyphInFont(ImWchar c) { ImFontAtlas* atlas = OwnerAtlas; - ImFontAtlas_FontHookRemapCodepoint(atlas, this, &c); + ImWchar src_codepoint = c; + int src_font_idx = -1, src_glyph_idx = -1; + ImFontAtlas_FontHookRemapCodepoint(atlas, this, c, &src_codepoint, &src_font_idx, &src_glyph_idx); + if (src_font_idx != -1) + { + ImFontConfig* src = Sources[src_font_idx]; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + return loader->FontSrcContainsGlyph ? loader->FontSrcContainsGlyph(atlas, src, src_glyph_idx) : false; + } for (ImFontConfig* src : Sources) { const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; - if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) + if (loader->FontSrcGetGlyphIndexFromCodepoint(atlas, src, c) != 0) return true; } return false; @@ -5674,7 +7201,6 @@ ImVec2 ImFontCalcTextSizeEx(ImFont* font, float size, float max_width, float wra text_end_display = text_end; ImFontBaked* baked = font->GetFontBaked(size); - // [PCSX2 Custom] const float line_height = ImCeil(size * font->LineHeight); const float scale = size / baked->Size; @@ -5775,7 +7301,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = pos.x; float y = pos.y; - if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) + if ((draw_list->Flags & ImDrawFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); @@ -5817,7 +7343,8 @@ begin: float y = pos.y; if (y > clip_rect.w) return; - if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) + flags |= draw_list->Flags; + if ((flags & ImDrawFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); @@ -6085,7 +7612,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, thickness); + draw_list->PathStroke(col, thickness, ImDrawFlags_AALineEnds); } // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. @@ -6167,7 +7694,7 @@ void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e); // BR } } - draw_list->PathFillConvex(col); + draw_list->PathFillConvex(col, ImDrawFlags_JoinMiter); } void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding) @@ -6176,14 +7703,14 @@ void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const bool fill_R = (inner.Max.x < outer.Max.x); const bool fill_U = (inner.Min.y > outer.Min.y); const bool fill_D = (inner.Max.y < outer.Max.y); - if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft)); - if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight)); - if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight)); - if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight)); - if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft); - if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight); - if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft); - if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); + if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_U ? 0 : ImDrawFlags_RoundTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundBottomLeft)); + if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_U ? 0 : ImDrawFlags_RoundTopRight) | (fill_D ? 0 : ImDrawFlags_RoundBottomRight)); + if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundNone | (fill_L ? 0 : ImDrawFlags_RoundTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundTopRight)); + if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_L ? 0 : ImDrawFlags_RoundBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundBottomRight)); + if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundTopLeft); + if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundTopRight); + if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundBottomLeft); + if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundBottomRight); } ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold) @@ -6192,43 +7719,51 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe bool round_r = r_in.Max.x >= r_outer.Max.x - threshold; bool round_t = r_in.Min.y <= r_outer.Min.y + threshold; bool round_b = r_in.Max.y >= r_outer.Max.y - threshold; - return ImDrawFlags_RoundCornersNone - | ((round_t && round_l) ? ImDrawFlags_RoundCornersTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundCornersTopRight : 0) - | ((round_b && round_l) ? ImDrawFlags_RoundCornersBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundCornersBottomRight : 0); + return ImDrawFlags_RoundNone + | ((round_t && round_l) ? ImDrawFlags_RoundTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundTopRight : 0) + | ((round_b && round_l) ? ImDrawFlags_RoundBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundBottomRight : 0); } // Helper for ColorPicker4() // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. -// FIXME: uses ImGui::GetColorU32 -void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) +void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float alpha, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags = ImDrawFlags_RoundCornersDefault_; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags = ImDrawFlags_RoundAll; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { - ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); - ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); - draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); + IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); + const ImU32 a_mask = (ImU32)(alpha * 255.0f) << IM_COL32_A_SHIFT; + ImU32 col_bg1 = (ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col) & ~IM_COL32_A_MASK) | a_mask; + ImU32 col_bg2 = (ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col) & ~IM_COL32_A_MASK) | a_mask; + const bool dual_layer = (alpha == 1.0f); // Dual layer is faster but incorrect if blending. + if (dual_layer) + draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); int yi = 0; for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) { - float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + float y1 = ImClamp((float)(int)y, p_min.y, p_max.y), y2 = ImMin((float)(int)(y + grid_step), p_max.y); if (y2 <= y1) continue; - for (float x = p_min.x + grid_off.x + ((yi ^ 1) & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + + const float x_start_off = dual_layer ? (((yi ^ 1) & 1) * grid_step) : 0.0f; + const float x_step = dual_layer ? (grid_step * 2.0f) : (grid_step); + int xi = 0; + for (float x = p_min.x + grid_off.x + x_start_off; x < p_max.x; x += x_step, xi++) { - float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + float x1 = ImClamp((float)(int)x, p_min.x, p_max.x), x2 = ImMin((float)(int)(x + grid_step), p_max.x); if (x2 <= x1) continue; - ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() - if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } - if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } + ImDrawFlags cell_flags = ImDrawFlags_RoundNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() + if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundTopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundBottomRight; } // Combine flags - cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); - draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); + cell_flags = (flags == ImDrawFlags_RoundNone || cell_flags == ImDrawFlags_RoundNone) ? ImDrawFlags_RoundNone : (cell_flags & flags); + ImU32 col_bg = dual_layer ? col_bg2 : (((yi + xi) & 1) ? col_bg2 : col_bg1); + draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg, rounding, cell_flags); } } } diff --git a/3rdparty/imgui/src/imgui_freetype.cpp b/3rdparty/imgui/src/imgui_freetype.cpp index f21c5a1ddf..b8efd76821 100644 --- a/3rdparty/imgui/src/imgui_freetype.cpp +++ b/3rdparty/imgui/src/imgui_freetype.cpp @@ -7,6 +7,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026/09/25: fixed handling of ExtraSizeScale. // 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support. // 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927) // 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics. @@ -220,17 +221,14 @@ void ImGui_ImplFreeType_FontSrcData::CloseFont() } } -static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint) +static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t glyph_index) { - uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint); - if (glyph_index == 0) - return nullptr; - // If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts. // - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076 // - https://github.com/ocornut/imgui/issues/4567 // - https://github.com/ocornut/imgui/issues/4566 // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version. + IM_ASSERT(glyph_index != 0); FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags); if (error) return nullptr; @@ -416,17 +414,15 @@ static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src return true; } -static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas*, ImFontConfig* src) { - IM_UNUSED(atlas); ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; IM_DELETE(bd_font_data); src->FontLoaderData = nullptr; } -static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) +static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas*, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { - IM_UNUSED(atlas); float size = baked->Size; const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; if (src->MergeMode && src->SizePixels != 0.0f) @@ -463,18 +459,19 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s // Read metrics FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale); - baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive). - baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative). + baked->Ascent = ((float)metrics.ascender / FT_SCALEFACTOR) * scale; // The pixel extents above the baseline in pixels (typically positive). + baked->Descent = ((float)metrics.descender / FT_SCALEFACTOR) * scale; // The extents below the baseline in pixels (typically negative). //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. //LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent. //MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font. + baked->Ascent = ImRoundSigned64(baked->Ascent * rasterizer_density) / rasterizer_density; + baked->Descent = ImRoundSigned64(baked->Descent * rasterizer_density) / rasterizer_density; } return true; } -static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) +static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas*, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src) { - IM_UNUSED(atlas); IM_UNUSED(baked); IM_UNUSED(src); ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src; @@ -483,13 +480,9 @@ static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE() } -static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) +static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, int glyph_index, ImFontGlyph* out_glyph, float* out_advance_x) { ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; - uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); - if (glyph_index == 0) - return false; - if (bd_font_data->BakedLastActivated != baked) // <-- could use id { // Activate current size @@ -498,7 +491,7 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf bd_font_data->BakedLastActivated = baked; } - const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint); + const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, glyph_index); if (metrics == nullptr) return false; @@ -508,9 +501,9 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf // Load metrics only mode const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density; - if (out_advance_x != NULL) + if (out_advance_x != nullptr) { - IM_ASSERT(out_glyph == NULL); + IM_ASSERT(out_glyph == nullptr); *out_advance_x = advance_x; return true; } @@ -527,7 +520,6 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf const bool is_visible = (w != 0 && h != 0); // Prepare glyph - out_glyph->Codepoint = codepoint; out_glyph->AdvanceX = advance_x; // Pack and retrieve position inside texture atlas @@ -570,12 +562,16 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf return true; } -static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +static int ImGui_ImplFreetype_FontSrcGetGlyphIndexFromCodepoint(ImFontAtlas*, ImFontConfig* src, ImWchar codepoint) { - IM_UNUSED(atlas); ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; - int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint); - return glyph_index != 0; + return (int)FT_Get_Char_Index(bd_font_data->FtFace, codepoint); +} + +static bool ImGui_ImplFreeType_FontSrcContainsGlyph(ImFontAtlas*, ImFontConfig* src, int glyph_index) +{ + ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; + return glyph_index > 0 && glyph_index < bd_font_data->FtFace->num_glyphs; } const ImFontLoader* ImGuiFreeType::GetFontLoader() @@ -586,7 +582,8 @@ const ImFontLoader* ImGuiFreeType::GetFontLoader() loader->LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown; loader->FontSrcInit = ImGui_ImplFreeType_FontSrcInit; loader->FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy; - loader->FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph; + loader->FontSrcGetGlyphIndexFromCodepoint = ImGui_ImplFreetype_FontSrcGetGlyphIndexFromCodepoint; + loader->FontSrcContainsGlyph = ImGui_ImplFreeType_FontSrcContainsGlyph; loader->FontBakedInit = ImGui_ImplFreeType_FontBakedInit; loader->FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy; loader->FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph; diff --git a/3rdparty/imgui/src/imgui_tables.cpp b/3rdparty/imgui/src/imgui_tables.cpp index f8fab3778b..a1757ed3f5 100644 --- a/3rdparty/imgui/src/imgui_tables.cpp +++ b/3rdparty/imgui/src/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (tables and columns code) /* @@ -375,7 +375,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG const int previous_frame_active = table->LastFrameActive; const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1; const ImGuiTableFlags previous_flags = table->Flags; - const bool is_new_table = (previous_frame_active == -1); table->ID = id; table->Flags = flags; table->LastFrameActive = g.FrameCount; @@ -384,6 +383,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->IsLayoutLocked = false; table->InnerWidth = inner_width; table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent; + table->IsNewTable = (previous_frame_active == -1); temp_data->UserOuterSize = outer_size; // Instance data (for instance 0, TableID == TableInstanceID) @@ -428,11 +428,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG SetNextWindowScroll(ImVec2(0.0f, 0.0f)); // Create scrolling region (without border and zero window padding) - ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None; - ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None; - if (flags & ImGuiTableFlags_ScrollX) - child_window_flags |= ImGuiWindowFlags_HorizontalScrollbar; - BeginChildEx(name, instance_id, outer_rect.GetSize(), child_child_flags, child_window_flags); + ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -588,7 +585,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsInitializing) { // Initialize - if (is_new_table) + if (table->IsNewTable) { table->SettingsOffset = -1; table->IsSettingsRequestLoad = true; @@ -634,7 +631,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() if (table->ColumnsNames.Buf.Size > 0) table->ColumnsNames.Buf.resize(0); - + return true; } @@ -1759,7 +1756,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo ImGuiTableColumn* column = &table->Columns[column_idx]; // If topology change goes into reconcile mode - if (table->IsReconcileMode == false && column->ID != column_id) + if (table->IsReconcileMode == false && column->ID != column_id && !table->IsNewTable) { table->IsReconcileMode = true; table->TempData->ReconcileColumnsRequests.reserve(table->ColumnsCount - column_idx); @@ -2964,7 +2961,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; - const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); + const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); const float draw_y2_body = table->InnerRect.Max.y; const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) @@ -3021,7 +3018,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if (table->Flags & ImGuiTableFlags_BordersOuterV) { inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); - inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Max.x - border_size, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { @@ -3032,9 +3029,10 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { // Draw bottom-most row border between it is above outer border. + const float h_inset = (table->Flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) - inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); + inner_drawlist->AddLineH(table->BorderX1 + h_inset, table->BorderX2 - h_inset, border_y, table->BorderColorLight, border_size); } inner_drawlist->PopClipRect(); @@ -3627,7 +3625,8 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - draw_list->AddLine(bg_shape[0] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), bg_shape[3] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); + const float border_size = TABLE_BORDER_SIZE; + draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); } } PopClipRect(); @@ -3973,7 +3972,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column->SortOrder != -1) - settings->SaveFlags |= ImGuiTableFlags_Sortable; + settings->SaveFlags |= ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable; // Because SortOrder saving itself is gated, make sure every column is saved (#9519) if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; } @@ -4110,6 +4109,12 @@ static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void* // Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2) void ImGui::TableFixDisplayOrder(ImGuiTable* table) { + if ((table->Flags & ImGuiTableFlags_Reorderable) == 0) + { + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n; + return; + } ImGuiContext& g = *GImGui; g.TempBuffer.reserve((int)(sizeof(ImGuiTableFixDisplayOrderColumnData) * table->ColumnsCount)); // FIXME: Maybe wrap those two lines as a helper. ImGuiTableFixDisplayOrderColumnData* fdo_columns = (ImGuiTableFixDisplayOrderColumnData*)(void*)g.TempBuffer.Data; diff --git a/3rdparty/imgui/src/imgui_widgets.cpp b/3rdparty/imgui/src/imgui_widgets.cpp index 5ed7d6febf..821b15f441 100644 --- a/3rdparty/imgui/src/imgui_widgets.cpp +++ b/3rdparty/imgui/src/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 // (widgets code) /* @@ -342,18 +342,18 @@ void ImGui::TextWrappedV(const char* fmt, va_list args) PopTextWrapPos(); } -void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...) +void ImGui::TextAligned(float align_x, float width, const char* fmt, ...) { va_list args; va_start(args, fmt); - TextAlignedV(align_x, size_x, fmt, args); + TextAlignedV(align_x, width, fmt, args); va_end(args); } // align_x: 0.0f = left, 0.5f = center, 1.0f = right. -// size_x : 0.0f = shortcut for GetContentRegionAvail().x -// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) -void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args) +// width < 0.0f (e.g. -FLT_MIN): shortcut for GetContentRegionAvail().x +// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024, #9455) +void ImGui::TextAlignedV(float align_x, float width, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) @@ -362,23 +362,25 @@ void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list a const char* text, *text_end; ImFormatStringToTempBufferV(&text, &text_end, fmt, args); const ImVec2 text_size = CalcTextSize(text, text_end); - size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x; + const ImVec2 size = CalcItemSize(ImVec2(width, 0.0f), 0.0f, text_size.y); ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y); - ImVec2 size(ImMin(size_x, text_size.x), text_size.y); + ImVec2 pos_max(pos.x + size.x, window->ClipRect.Max.y); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x); window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x); - if (align_x > 0.0f && text_size.x < size_x) - pos.x += ImTrunc((size_x - text_size.x) * align_x); - RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); - const ImVec2 backup_max_pos = window->DC.CursorMaxPos; ItemSize(size); - ItemAdd(ImRect(pos, pos + size), 0); - window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up. + const bool is_visible = ItemAdd(ImRect(pos, pos + size), 0); + window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content to 'size' to use 'text_size' allowing auto-resize to function with align_x > 0.0f + if (!is_visible) + return; - if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) + if (align_x > 0.0f && text_size.x < size.x) + pos.x += ImTrunc((size.x - text_size.x) * align_x); + RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); + + if (size.x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) SetTooltip("%.*s", (int)(text_end - text), text); } @@ -930,7 +932,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); const ImU32 cross_col = GetColorU32(ImGuiCol_Text); - const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + const ImVec2 cross_center = bb.GetCenter(); const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); @@ -1396,7 +1398,7 @@ bool ImGui::RadioButton(const char* label, bool active) RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); - if (active) + if (active && (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) == 0) { const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); @@ -1755,7 +1757,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; - const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); + float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f) + 1.0f; const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -1769,9 +1771,9 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x2 = label_pos.x - style.ItemSpacing.x; const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) - window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) - window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); @@ -1781,7 +1783,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end if (g.LogEnabled) LogText("---"); if (separator_thickness > 0.0f) - window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); } } @@ -1949,7 +1951,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF const ImGuiID id = window->GetID(label); IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together if (flags & ImGuiComboFlags_WidthFitPreview) - IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + IM_ASSERT((flags & ImGuiComboFlags_NoPreview) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); const char* label_end = FindRenderedTextEnd(label); @@ -1978,24 +1980,19 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); RenderNavCursor(bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) - window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); + window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundLeft); if (!(flags & ImGuiComboFlags_NoArrowButton)) { ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); - window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundRight); if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); } RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); - // Custom preview - if (flags & ImGuiComboFlags_CustomPreview) - { - g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); - IM_ASSERT(preview_value == NULL || preview_value[0] == 0); - preview_value = NULL; - } + // Store geometry for BeginComboPreview() - only necessary when visible. + g.ComboPreviewData.PreviewRect = (flags & ImGuiComboFlags_NoPreview) ? ImRect() : ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); // Render preview and label if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) @@ -2077,6 +2074,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags return false; } g.BeginComboDepth++; + g.CurrentWindowStack.back().ParentLastComboPreviewRect = g.ComboPreviewData.PreviewRect; return true; } @@ -2084,6 +2082,7 @@ void ImGui::EndCombo() { ImGuiContext& g = *GImGui; g.BeginComboDepth--; + g.ComboPreviewData.PreviewRect = g.CurrentWindowStack.back().ParentLastComboPreviewRect; char name[16]; ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers? if (strcmp(g.CurrentWindow->Name, name) != 0) @@ -2091,19 +2090,26 @@ void ImGui::EndCombo() EndPopup(); } -// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements -// (Experimental, see GitHub issues: #1658, #4168) +// Submit preview contents for BeginCombo()/EndCombo(), to display contents that's more than just a text label. +// - [BETA] See GitHub issues: #1658, #4168. +// - Make sure you call this after EndCombo(). +// - The preview is designed to only host non-interactive elements. +// - Not compatible with ImGuiComboFlags_WidthFitPreview. +// - 2026-09-08 (1.93.0): removed ImGuiComboFlags_CustomPreview. You can use BeginComboPreview()/EndComboPreview() without an extra flag. bool ImGui::BeginComboPreview() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) return false; - IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? - if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + if (!window->ClipRect.Overlaps(preview_data->PreviewRect) || preview_data->PreviewRect.GetWidth() <= 0.0f) // Narrower test (optional) + handle _NoPreview return false; + IM_ASSERT_USER_ERROR_RETV(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y, false, + "Call BeginComboPreview() after BeginCombo(), not after EndCombo()!"); // Calling after EndCombo() works only if you don't nest combos. + IM_ASSERT_USER_ERROR_RETV(preview_data->WithinPreview == false, false, + "Cannot recurse BeginComboPreview(): call EndComboPreview() before opening another combo."); // FIXME: This could be contained in a PushWorkRect() api preview_data->BackupCursorPos = window->DC.CursorPos; @@ -2111,6 +2117,10 @@ bool ImGui::BeginComboPreview() preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; preview_data->BackupLayout = window->DC.LayoutType; + preview_data->WithinPreview = true; + preview_data->BackupWorkRectMaxX = window->WorkRect.Max.x; + preview_data->BackupContentRectMaxX = window->ContentRegionRect.Max.x; + window->WorkRect.Max.x = window->ContentRegionRect.Max.x = preview_data->PreviewRect.Max.x - g.Style.FramePadding.x; window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; window->DC.CursorMaxPos = window->DC.CursorPos; window->DC.LayoutType = ImGuiLayoutType_Horizontal; @@ -2136,12 +2146,14 @@ void ImGui::EndComboPreview() } PopClipRect(); window->DC.CursorPos = preview_data->BackupCursorPos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); // No need to do the same with IdealMaxPos window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->WorkRect.Max.x = preview_data->BackupWorkRectMaxX; + window->ContentRegionRect.Max.x = preview_data->BackupContentRectMaxX; window->DC.LayoutType = preview_data->BackupLayout; window->DC.IsSameLine = false; - preview_data->PreviewRect = ImRect(); + preview_data->WithinPreview = false; } // Getter for the old Combo() API: const char*[] @@ -2174,7 +2186,9 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo // Call the getter to obtain the preview string which is a parameter to BeginCombo() const char* preview_value = NULL; - if (*current_item >= 0 && *current_item < items_count) + if ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_MixedValue) + preview_value = ""; + else if (*current_item >= 0 && *current_item < items_count) preview_value = getter(user_data, *current_item); // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. @@ -2658,19 +2672,39 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const return true; } +static bool ShortcutsForCancel(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + bool is_cancel_with_keyboard = ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, id); + bool is_cancel_with_gamepad = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0 && ImGui::Shortcut(ImGuiKey_NavGamepadCancel, ImGuiInputFlags_None, id); + bool is_cancel_with_mouse = ImGui::IsMouseReleased(ImGuiMouseButton_Right, id); + if (is_cancel_with_mouse) + ImGui::SetKeyOwner(ImGuiKey_MouseRight, id); + return is_cancel_with_keyboard || is_cancel_with_gamepad || is_cancel_with_mouse; +} + bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + // Things we can do easily outside the DragBehaviorT<> template, saves code generation. ImGuiContext& g = *GImGui; if (g.ActiveId == id) { - // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) ClearActiveID(); else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } } if (g.ActiveId != id) return false; @@ -2793,11 +2827,16 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, MarkItemEdited(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3004,7 +3043,7 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T result = 0.0f; // Workaround for values that are in-range but below our fudge else if (v_clamped >= v_max_fudged) result = 1.0f; // Workaround for values that are in-range but above our fudge - else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions + else if (((FLOATTYPE)v_min * (FLOATTYPE)v_max) < 0.0f) // Range crosses zero, so split into two portions { float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; @@ -3058,7 +3097,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range - if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts + if (((FLOATTYPE)v_min * (FLOATTYPE)v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts { float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; @@ -3083,13 +3122,13 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T { result = ImLerp(v_min, v_max, t); } - else if (t < 1.0) + else // t is already < 1.0 here { // - For integer values we want the clicking position to match the grab box so we round above // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; + FLOATTYPE v_new_off_f = (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min) * (FLOATTYPE)t; result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); } } @@ -3138,37 +3177,24 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ float clicked_t = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse) { - if (!g.IO.MouseDown[0]) + const float mouse_pos = g.IO.MousePos[axis]; + if (g.ActiveIdIsJustActivated) { - ClearActiveID(); - } - else - { - const float mouse_abs_pos = g.IO.MousePos[axis]; - if (g.ActiveIdIsJustActivated) - { - float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); - if (axis == ImGuiAxis_Y) - grab_t = 1.0f - grab_t; - const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); - const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. - g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; - } - if (slider_usable_sz > 0.0f) - clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) - clicked_t = 1.0f - clicked_t; - set_new_value = true; + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + const bool clicked_around_grab = (mouse_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_pos - grab_pos : 0.0f; } + if (slider_usable_sz > 0.0f) + clicked_t = ImSaturate((mouse_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { - if (g.ActiveIdIsJustActivated) - { - g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation - g.SliderCurrentAccumDirty = false; - } - float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis); if (input_delta != 0.0f) { @@ -3196,11 +3222,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } float delta = g.SliderCurrentAccum; - if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) - { - ClearActiveID(); - } - else if (g.SliderCurrentAccumDirty) + if (g.SliderCurrentAccumDirty) { clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); @@ -3226,7 +3248,6 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); } - g.SliderCurrentAccumDirty = false; } } @@ -3281,6 +3302,33 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() + // Things we can do easily outside the SliderBehaviorT<> template, saves code generation. + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + if (g.ActiveIdIsJustActivated) + { + g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } + } + switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } @@ -3391,11 +3439,16 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3545,9 +3598,15 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); + if (is_mixed) + PopStyleColor(); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3633,6 +3692,7 @@ const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_ const char* fmt_end = ImParseFormatFindEnd(fmt_start); if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. return fmt_start; + buf[0] = 0; // Fix false positive with caller assuming that buf could be non-initialized (#9508) ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); return buf; } @@ -3784,6 +3844,10 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; + if (value_changed) MarkItemEdited(id); return value_changed; @@ -3860,6 +3924,9 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); } } + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; // Step buttons if (has_step_buttons) @@ -4165,7 +4232,7 @@ static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_ const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks); if (p == p_cursor) // If we are already on a visible beginning-of-line, return real beginning-of-line (would be same as regular handler below) return (int)(p_bol - obj->TextSrc); - if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left) + if (p_eol == p_cursor && obj->TextSrc[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left) return (int)(p_bol - obj->TextSrc); if (p_eol >= p_cursor) return (int)(p - obj->TextSrc); @@ -4728,7 +4795,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) - IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not not work with left-trimming + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not work with left-trimming IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0); // WordWrap does not work with Password mode. IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0); // WordWrap does not work in single-line mode. @@ -4825,6 +4892,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! @@ -4911,7 +4979,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_multiline) { - if (flags & ImGuiInputTextFlags_AutoSelectAll) + if ((flags & ImGuiInputTextFlags_AutoSelectAll) || is_mixed) select_all = true; if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) select_all = true; @@ -4989,7 +5057,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; - bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextSrc : buf)[0] == 0) && !is_mixed; // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) @@ -4998,6 +5066,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (state != NULL && state->ID == id) { state->Flags = flags; + state->EditedThisFrame = state->ValidatedThisFrame = false; //state->LastFrameActive = g.FrameCount; // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) @@ -5013,7 +5082,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { IM_ASSERT(state != NULL); - state->EditedThisFrame = false; state->BufCapacity = buf_size; state->WrapWidth = wrap_width; @@ -5215,7 +5283,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { if (flags & ImGuiInputTextFlags_EscapeClearsAll) { - if (state->TextA.Data[0] != 0) + if (state->TextSrc[0] != 0) { revert_edit = true; } @@ -5308,14 +5376,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (flags & ImGuiInputTextFlags_EscapeClearsAll) { // Clear input - IM_ASSERT(state->TextA.Data[0] != 0); + IM_ASSERT(state->TextSrc[0] != 0); apply_new_text = ""; apply_new_text_length = 0; value_changed = true; char empty_string = 0; stb_textedit_replace(state, state->Stb, &empty_string, 0); } - else if (strcmp(state->TextA.Data, state->TextToRevertTo.Data) != 0) + else if (strcmp(state->TextSrc, state->TextToRevertTo.Data) != 0) { apply_new_text = state->TextToRevertTo.Data; apply_new_text_length = state->TextToRevertTo.Size - 1; @@ -5416,7 +5484,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified - if (strcmp(state->TextSrc, buf) != 0) + if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && (state->EditedThisFrame || validated))) { apply_new_text = state->TextSrc; apply_new_text_length = state->TextLen; @@ -5426,7 +5494,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) - value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); + value_changed = (validated || clear_active_id || revert_edit) && (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)); apply_new_text = value_changed ? state->TextSrc : NULL; apply_new_text_length = value_changed ? state->TextLen : 0; } @@ -5511,7 +5579,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Display hint when contents is empty // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) - const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) @@ -5520,10 +5588,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); } - if (is_displaying_hint) + const bool is_displaying_mixed = is_mixed && g.ActiveId != id && apply_new_text == NULL; + if (is_displaying_mixed && g.MixedValueLabel != NULL) + { + buf_display = g.MixedValueLabel; + buf_display_end = buf_display + strlen(g.MixedValueLabel); + render_cursor = render_selection = false; + } + else if (is_displaying_hint) { buf_display = hint; - buf_display_end = hint + ImStrlen(hint); + buf_display_end = buf_display + ImStrlen(buf_display); } else { @@ -5563,7 +5638,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 draw_scroll; // Render text. We currently only render selection when the widget is active or while scrolling. - const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + const ImU32 text_col = GetColorU32(is_displaying_mixed ? ImGuiCol_TextMixedValue : is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); if (render_cursor || render_selection) { // Render text (with cursor and selection) @@ -5684,9 +5759,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + ImRect cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize); + draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize, ImDrawFlags_StrokeInside); // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // This is required for some backends (SDL3) to start emitting character/text inputs. @@ -5736,6 +5811,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); + if (state && validated) + state->ValidatedThisFrame = true; if (value_changed) MarkItemEdited(id); @@ -6012,7 +6089,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + PushItemFlag(ImGuiItemFlags_MixedValue, false); value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemFlag(); } EndPopup(); } @@ -6126,6 +6205,10 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl const bool is_readonly = ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; g.NextItemData.ClearFlags(); + const float backup_alpha = g.Style.Alpha; + if (g.DisabledStackSize > 0) + g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. + PushID(label); const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); if (set_current_color_edit_id) @@ -6283,6 +6366,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); @@ -6300,6 +6384,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } } PopItemFlag(); + PopItemFlag(); EndGroup(); } @@ -6465,7 +6550,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl { float alpha = ImSaturate(col[3]); ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); - RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), style.Alpha, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); @@ -6483,6 +6568,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl g.ColorEditCurrentID = 0; PopID(); + if (g.DisabledStackSize > 0) + g.Style.Alpha = backup_alpha; + return value_changed; } @@ -6515,34 +6603,42 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); - ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); - float grid_step = ImMin(size.x, size.y) / 2.99f; - float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); - ImRect bb_inner = bb; - float off = 0.0f; - if ((flags & ImGuiColorEditFlags_NoBorder) == 0) + const ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); + const float grid_step = ImMin(size.x, size.y) / 2.99f; + const float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + + const float backup_alpha = g.Style.Alpha; + if (g.DisabledStackSize > 0) + g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. + if (is_mixed) { - off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. - bb_inner.Expand(off); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); + if (g.MixedValueLabel != NULL) + RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + PopStyleColor(); } - if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) + else if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { - float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); + float mid_x = IM_ROUND((bb.Min.x + bb.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step, 0.0f), rounding, ImDrawFlags_RoundRight); else - window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); - window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); + window->DrawList->AddRectFilled(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundRight); + window->DrawList->AddRectFilled(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundLeft); } else { // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb; if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb.Min, bb.Max, ColorConvertFloat4ToU32(col_source), g.Style.Alpha, grid_step, ImVec2(0.0f, 0.0f), rounding); else - window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(col_source), rounding); } + if (g.DisabledStackSize > 0) + g.Style.Alpha = backup_alpha; RenderNavCursor(bb, id); if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { @@ -6560,7 +6656,9 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); else SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton(desc_id, col, flags); + PopItemFlag(); SameLine(); TextEx("Color"); EndDragDropSource(); @@ -6604,7 +6702,9 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_; + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz); + PopItemFlag(); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { @@ -6886,7 +6986,7 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) } // Store ImGuiTreeNodeStackData for just submitted node. -// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. +// Currently only supports 32 level deep. Easy to increase to e.g. 64. static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) { ImGuiContext& g = *GImGui; @@ -6909,6 +7009,9 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } +#define TREE_NODE_MAX_DEPTH 32 +#define TREE_NODE_GET_DEPTH_MASK(_DEPTH) (((ImU32)(_DEPTH) < TREE_NODE_MAX_DEPTH) ? (1u << (_DEPTH)) : 0u) + bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6985,7 +7088,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0) flags |= g.Style.TreeLinesFlags; const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f); - if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen) && window->DC.TreeDepth < TREE_NODE_MAX_DEPTH) { store_tree_node_stack_data = draw_tree_lines; if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive) @@ -6996,12 +7099,16 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; if (!is_visible) { - if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1)))) + if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) { - ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. - if (frame_bb.Min.y >= window->ClipRect.Max.y) - window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done + const ImU32 tree_depth_mask = TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth - 1); + if (window->DC.TreeRecordsClippedNodesY2Mask & tree_depth_mask) + { + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + if (frame_bb.Min.y >= window->ClipRect.Max.y) + window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask; // Done + } } if (is_open && store_tree_node_stack_data) TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() @@ -7198,20 +7305,22 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) + if ((window->DC.TreeHasStackDataDepthMask & TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth - 1)) == 0) return; + // FIXME: Could this be simplified using new stroke flags? ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - float x1 = ImTrunc(parent_data->DrawLinesX1); + const float half_size = g.Style.TreeLinesSize * 0.5f; + float x1 = ImTrunc(parent_data->DrawLinesX1 - half_size) + half_size; // Draw line centered at X1, but snap to pixels boundary. float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); - float y = ImTrunc(target_pos.y); + float y = ImTrunc(target_pos.y - half_size) + half_size; float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); if (x1 >= x2) return; - if (rounding > 0.0f) + if (rounding > 0.0f && rounding > half_size) { - x1 += 0.5f + rounding; + x1 += rounding; window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); @@ -7219,7 +7328,10 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) } else { - window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + // We use AddLine() instead of AddLineH() as we provide coordinates for line center, + // in order to make the code use same coordinates for both rounded and non-rounded version. (FIXME-OPT) + x1 += half_size; // Avoid overdraw + window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7242,10 +7354,12 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) y2 = ImMin(y2, window->ClipRect.Max.y); if (y2 <= y1) return; - float x = ImTrunc(data->DrawLinesX1); + float x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + + // We use AddLine() instead of AddLineV() as we provide coordinates for line center (FIXME-OPT) + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); } @@ -7282,8 +7396,7 @@ void ImGui::TreePop() Unindent(); window->DC.TreeDepth--; - ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - + ImU32 tree_depth_mask = TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth); if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) { const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; @@ -7510,7 +7623,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) // - (2) usage will fail with clipped items // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId && !g.NavJustMovedToIsInit) if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0) selected = pressed = auto_selected = true; } @@ -9031,7 +9144,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); - // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + // We could render lower-level to save a bit of CPU. ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); if (plot_type == ImGuiPlotType_Lines) @@ -9042,6 +9155,8 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get { if (pos1.x >= pos0.x + 2.0f) pos1.x -= 1.0f; + if (pos0.y > pos1.y) + ImSwap(pos0.y, pos1.y); window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); } @@ -10114,16 +10229,17 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } // Horizontal scrolling buttons - // Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + // - Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + // - Selection skips buttons and might cross through sections if there are Tabs in Leading/Trailing sections. const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink; tab_bar->ScrollButtonEnabled = ((width_all_tabs_to_use_for_scroll > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll); if (tab_bar->ScrollButtonEnabled) if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { - scroll_to_tab_id = scroll_and_select_tab->ID; if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) - tab_bar->SelectedTabId = scroll_to_tab_id; + tab_bar->SelectedTabId = scroll_and_select_tab->ID; + scroll_to_tab_id = scroll_and_select_tab->ID; } if (scroll_to_tab_id == 0 && scroll_to_selected_tab) scroll_to_tab_id = tab_bar->SelectedTabId; @@ -10296,6 +10412,14 @@ const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) return tab_bar->TabsNames.Buf.Data + tab->NameOffset; } +ImVec2 ImGui::TabBarGetTabPos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if ((tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0) + return tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + return tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); +} + // The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { @@ -10343,11 +10467,20 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); if (tab == NULL) return; + + // Clamp attempt to scroll to leading/trailing sections items. + // This could be done at the TabBarScrollingButtons() call site as well, but here works. + if (tab->Flags & ImGuiTabItemFlags_Leading) + tab = &tab_bar->Tabs[sections[0].TabCount]; + else if (tab->Flags & ImGuiTabItemFlags_Trailing) + tab = &tab_bar->Tabs[sections[0].TabCount + sections[1].TabCount]; if (tab->Flags & ImGuiTabItemFlags_SectionMask_) return; + // When scrolling to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + // Disable the margin if the scrolling section is too small for the target tab: prefer displaying a maximum of the label. ImGuiContext& g = *GImGui; - float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + float margin = ImClamp(tab_bar->ScrollingRectMaxX - tab_bar->ScrollingRectMinX - tab->Width, g.Style.ItemInnerSpacing.x, g.FontSize * 1.0f); int order = TabBarGetTabOrder(tab_bar, tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) @@ -10749,10 +10882,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Layout const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; size.x = tab->Width; - if (is_central_section) - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f); - else - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); + window->DC.CursorPos = TabBarGetTabPos(tab_bar, tab); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); @@ -10823,8 +10953,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) { // Might be moved to TabItemBackground() ? - ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); - ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImVec2 tl = bb.GetTL() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + ImVec2 tr = bb.GetTR() + ImVec2(0, style.TabBarOverlineSize * 0.5f); ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); if (style.TabRounding > 0.0f) { @@ -10835,7 +10965,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } else { - display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); + display_draw_list->AddLine(tl, tr, overline_col, style.TabBarOverlineSize); } } RenderNavCursor(bb, id); @@ -10924,14 +11054,14 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars. const float y2 = bb.Max.y - g.Style.TabBarBorderSize; - draw_list->AddRectFilled(bb.Min, ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop); + draw_list->AddRectFilled(ImVec2(bb.Min.x, y1), ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundTop); if (g.Style.TabBorderSize > 0.0f) { - draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); - draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); - draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); - draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize); + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize, ImDrawFlags_StrokeCenterBiased); // Use _StrokeInside? } } diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 7f124591b0..0760da3448 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -3317,9 +3317,8 @@ void Achievements::DrawLeaderboardsWindow() const float line_thickness = LayoutScale(1.0f); const float line_padding = LayoutScale(5.0f); - const ImVec2 line_start(bb.Min.x, bb.Min.y + GetLineHeight(g_large_font) + line_padding); - const ImVec2 line_end(bb.Max.x, line_start.y); - ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); + const float line_y = bb.Min.y + GetLineHeight(g_large_font) + line_padding; + ImGui::GetWindowDrawList()->AddLineH(bb.Min.x, bb.Max.x, line_y, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); } } } diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp index 672e39c726..8db1c34ed7 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.cpp +++ b/pcsx2/ImGui/ImGuiFullscreen.cpp @@ -1205,11 +1205,10 @@ void ImGuiFullscreen::DrawWindowTitle(const char* title) RenderTextClippedWithShadow(rect.Min, rect.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &rect); ImGui::PopFont(); - const ImVec2 line_start(pos.x, pos.y + g_large_font.second + LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING)); - const ImVec2 line_end(pos.x + size.x, line_start.y); + const float line_y = pos.y + g_large_font.second + LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING); const float line_thickness = LayoutScale(1.0f); ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddLine(line_start, line_end, IM_COL32(255, 255, 255, 255), line_thickness); + dl->AddLineH(pos.x, pos.x + size.x, line_y, IM_COL32(255, 255, 255, 255), line_thickness); } void ImGuiFullscreen::GetMenuButtonFrameBounds(float height, ImVec2* pos, ImVec2* size) @@ -1363,9 +1362,8 @@ void ImGuiFullscreen::MenuHeading(const char* title, bool draw_line /*= true*/) if (draw_line) { - const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font.second + line_padding); - const ImVec2 line_end(bb.Max.x, line_start.y); - ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); + const float line_y = bb.Min.y + g_large_font.second + line_padding; + ImGui::GetWindowDrawList()->AddLineH(bb.Min.x, bb.Max.x, line_y, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); } } @@ -1399,9 +1397,8 @@ bool ImGuiFullscreen::MenuHeadingButton( if (draw_line) { - const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font.second + line_padding); - const ImVec2 line_end(bb.Max.x, line_start.y); - ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); + const float line_y = bb.Min.y + g_large_font.second + line_padding; + ImGui::GetWindowDrawList()->AddLineH(bb.Min.x, bb.Max.x, line_y, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); } return pressed; @@ -3239,9 +3236,9 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) ImDrawList* dl = ImGui::GetForegroundDrawList(); dl->AddRectFilled(ImVec2(box_min.x + shadow_size, box_min.y + shadow_size), ImVec2(box_max.x + shadow_size, box_max.y + shadow_size), - IM_COL32(20, 20, 20, (180 * opacity) / 255u), rounding, ImDrawFlags_RoundCornersAll); - dl->AddRectFilled(box_min, box_max, background_color, rounding, ImDrawFlags_RoundCornersAll); - dl->AddRect(box_min, box_max, border_color, rounding, ImGuiFullscreen::LayoutScale(1.0f), ImDrawFlags_RoundCornersAll); + IM_COL32(20, 20, 20, (180 * opacity) / 255u), rounding, ImDrawFlags_RoundAll); + dl->AddRectFilled(box_min, box_max, background_color, rounding, ImDrawFlags_RoundAll); + dl->AddRect(box_min, box_max, border_color, rounding, ImGuiFullscreen::LayoutScale(1.0f), ImDrawFlags_RoundAll); if (has_badge) { diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index e824f6772e..53abe0f3f9 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -702,13 +702,13 @@ bool ImGuiManager::AddEmojiFont() // See https://github.com/ocornut/imgui/issues/8240 static ImFontLoader filter_loader; filter_loader.Name = "Emoji Preprocessor"; - filter_loader.FontSrcContainsGlyph = [](ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) { + filter_loader.FontSrcGetGlyphIndexFromCodepoint = [](ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) -> int { if (codepoint == 0xfe0e || codepoint == 0xfe0f) - return true; - return false; + return static_cast(codepoint); + return 0; }; - filter_loader.FontBakedLoadGlyph = [](ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) { - if (codepoint != 0xfe0e && codepoint != 0xfe0f) + filter_loader.FontBakedLoadGlyph = [](ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, int glyph_index, ImFontGlyph* out_glyph, float* out_advance_x) { + if (glyph_index != 0xfe0e && glyph_index != 0xfe0f) return false; // Metrics only mode @@ -718,7 +718,6 @@ bool ImGuiManager::AddEmojiFont() return true; } - out_glyph->Codepoint = codepoint; out_glyph->AdvanceX = 0.0f; out_glyph->Visible = false; diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index b40db93ff7..ea07c5f744 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -719,7 +719,7 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f const float grid_y = plot_br.y - frac * graph_size.y; const float ly = grid_y - font_size * 0.5f; - dl->AddLine(ImVec2(plot_tl.x, grid_y), ImVec2(plot_br.x, grid_y), IM_COL32(255, 255, 255, 40), 1.0f); + dl->AddLineH(plot_tl.x, plot_br.x, grid_y, IM_COL32(255, 255, 255, 40), 1.0f); s.format("{:.1f}", min_val + (max_val - min_val) * frac); const float left_text_w = osd_font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, s.c_str(), s.c_str() + s.length()).x;