|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
// dear imgui, v1.85 WIP
|
|
|
|
|
// dear imgui, v1.89.2 WIP
|
|
|
|
|
// (main code and documentation)
|
|
|
|
|
|
|
|
|
|
// Help:
|
|
|
|
@ -11,7 +11,7 @@
|
|
|
|
|
// - FAQ http://dearimgui.org/faq
|
|
|
|
|
// - Homepage & latest https://github.com/ocornut/imgui
|
|
|
|
|
// - Releases & changelog https://github.com/ocornut/imgui/releases
|
|
|
|
|
// - Gallery https://github.com/ocornut/imgui/issues/4451 (please post your screenshots/video there!)
|
|
|
|
|
// - Gallery https://github.com/ocornut/imgui/issues/5243 (please post your screenshots/video there!)
|
|
|
|
|
// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there)
|
|
|
|
|
// - Glossary https://github.com/ocornut/imgui/wiki/Glossary
|
|
|
|
|
// - Issues & support https://github.com/ocornut/imgui/issues
|
|
|
|
@ -65,11 +65,13 @@ CODE
|
|
|
|
|
// [SECTION] MISC HELPERS/UTILITIES (Color functions)
|
|
|
|
|
// [SECTION] ImGuiStorage
|
|
|
|
|
// [SECTION] ImGuiTextFilter
|
|
|
|
|
// [SECTION] ImGuiTextBuffer
|
|
|
|
|
// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
|
|
|
|
|
// [SECTION] ImGuiListClipper
|
|
|
|
|
// [SECTION] STYLING
|
|
|
|
|
// [SECTION] RENDER HELPERS
|
|
|
|
|
// [SECTION] INITIALIZATION, SHUTDOWN
|
|
|
|
|
// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
|
|
|
|
|
// [SECTION] INPUTS
|
|
|
|
|
// [SECTION] ERROR CHECKING
|
|
|
|
|
// [SECTION] LAYOUT
|
|
|
|
|
// [SECTION] SCROLLING
|
|
|
|
@ -79,9 +81,12 @@ CODE
|
|
|
|
|
// [SECTION] DRAG AND DROP
|
|
|
|
|
// [SECTION] LOGGING/CAPTURING
|
|
|
|
|
// [SECTION] SETTINGS
|
|
|
|
|
// [SECTION] VIEWPORTS
|
|
|
|
|
// [SECTION] LOCALIZATION
|
|
|
|
|
// [SECTION] VIEWPORTS, PLATFORM WINDOWS
|
|
|
|
|
// [SECTION] PLATFORM DEPENDENT HELPERS
|
|
|
|
|
// [SECTION] METRICS/DEBUGGER WINDOW
|
|
|
|
|
// [SECTION] DEBUG LOG WINDOW
|
|
|
|
|
// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL)
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
@ -99,6 +104,7 @@ CODE
|
|
|
|
|
- Easy to hack and improve.
|
|
|
|
|
- Minimize setup and maintenance.
|
|
|
|
|
- Minimize state storage on user side.
|
|
|
|
|
- Minimize state synchronization.
|
|
|
|
|
- Portable, minimize dependencies, run on target (consoles, phones, etc.).
|
|
|
|
|
- Efficient runtime and memory consumption.
|
|
|
|
|
|
|
|
|
@ -122,14 +128,13 @@ CODE
|
|
|
|
|
- Hold SHIFT or use mouse to select text.
|
|
|
|
|
- CTRL+Left/Right to word jump.
|
|
|
|
|
- CTRL+Shift+Left/Right to select words.
|
|
|
|
|
- CTRL+A our Double-Click to select all.
|
|
|
|
|
- CTRL+A or Double-Click to select all.
|
|
|
|
|
- CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
|
|
|
|
|
- CTRL+Z,CTRL+Y to undo/redo.
|
|
|
|
|
- ESCAPE to revert text to its original value.
|
|
|
|
|
- You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
|
|
|
|
|
- Controls are automatically adjusted for OSX to match standard OSX text editing operations.
|
|
|
|
|
- General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
|
|
|
|
|
- General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://dearimgui.org/controls_sheets
|
|
|
|
|
- General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. Download controller mapping PNG/PSD at http://dearimgui.org/controls_sheets
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PROGRAMMER GUIDE
|
|
|
|
@ -253,9 +258,9 @@ CODE
|
|
|
|
|
io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
|
|
|
|
|
io.DisplaySize.x = 1920.0f; // set the current display width
|
|
|
|
|
io.DisplaySize.y = 1280.0f; // set the current display height here
|
|
|
|
|
io.MousePos = my_mouse_pos; // set the mouse position
|
|
|
|
|
io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states
|
|
|
|
|
io.MouseDown[1] = my_mouse_buttons[1];
|
|
|
|
|
io.AddMousePosEvent(mouse_x, mouse_y); // update mouse position
|
|
|
|
|
io.AddMouseButtonEvent(0, mouse_b[0]); // update mouse button states
|
|
|
|
|
io.AddMouseButtonEvent(1, mouse_b[1]); // update mouse button states
|
|
|
|
|
|
|
|
|
|
// Call NewFrame(), after this point you can use ImGui::* functions anytime
|
|
|
|
|
// (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere)
|
|
|
|
@ -287,12 +292,14 @@ CODE
|
|
|
|
|
---------------------------------------------
|
|
|
|
|
The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function.
|
|
|
|
|
|
|
|
|
|
void void MyImGuiRenderFunction(ImDrawData* draw_data)
|
|
|
|
|
void MyImGuiRenderFunction(ImDrawData* draw_data)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
|
|
|
|
|
// TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering.
|
|
|
|
|
// TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
|
|
|
|
|
// TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
|
|
|
|
|
// TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
|
|
|
|
|
ImVec2 clip_off = draw_data->DisplayPos;
|
|
|
|
|
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
|
|
|
|
{
|
|
|
|
|
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
|
|
|
@ -307,9 +314,11 @@ CODE
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// The texture for the draw call is specified by pcmd->GetTexID().
|
|
|
|
|
// The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization.
|
|
|
|
|
MyEngineBindTexture((MyTexture*)pcmd->GetTexID());
|
|
|
|
|
// Project scissor/clipping rectangles into framebuffer space
|
|
|
|
|
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
|
|
|
|
|
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
|
|
|
|
|
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// We are using scissoring to clip some objects. All low-level graphics API should support it.
|
|
|
|
|
// - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
|
|
|
|
@ -320,14 +329,16 @@ CODE
|
|
|
|
|
// - In the interest of supporting multi-viewport applications (see 'docking' branch on github),
|
|
|
|
|
// always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space.
|
|
|
|
|
// - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
|
|
|
|
|
ImVec2 pos = draw_data->DisplayPos;
|
|
|
|
|
MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y));
|
|
|
|
|
MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y);
|
|
|
|
|
|
|
|
|
|
// The texture for the draw call is specified by pcmd->GetTexID().
|
|
|
|
|
// The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization.
|
|
|
|
|
MyEngineBindTexture((MyTexture*)pcmd->GetTexID());
|
|
|
|
|
|
|
|
|
|
// Render 'pcmd->ElemCount/3' indexed triangles.
|
|
|
|
|
// By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices.
|
|
|
|
|
MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
|
|
|
|
|
MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset);
|
|
|
|
|
}
|
|
|
|
|
idx_buffer += pcmd->ElemCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -336,31 +347,27 @@ CODE
|
|
|
|
|
USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
|
|
|
|
|
------------------------------------------
|
|
|
|
|
- The gamepad/keyboard navigation is fairly functional and keeps being improved.
|
|
|
|
|
- Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PS4, Switch, XB1) without a mouse!
|
|
|
|
|
- You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787
|
|
|
|
|
- Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PlayStation, Switch, Xbox) without a mouse!
|
|
|
|
|
- The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
|
|
|
|
|
- Keyboard:
|
|
|
|
|
- Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
|
|
|
|
|
NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
|
|
|
|
|
- When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
|
|
|
|
|
will be set. For more advanced uses, you may want to read from:
|
|
|
|
|
- Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
|
|
|
|
|
- When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard),
|
|
|
|
|
the io.WantCaptureKeyboard flag will be set. For more advanced uses, you may want to read from:
|
|
|
|
|
- io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
|
|
|
|
|
- io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
|
|
|
|
|
- or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
|
|
|
|
|
Please reach out if you think the game vs navigation input sharing could be improved.
|
|
|
|
|
- Gamepad:
|
|
|
|
|
- Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
|
|
|
|
|
- Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame().
|
|
|
|
|
Note that io.NavInputs[] is cleared by EndFrame().
|
|
|
|
|
- See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
|
|
|
|
|
0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
|
|
|
|
|
- We use a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
|
|
|
|
|
Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).
|
|
|
|
|
- Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
|
|
|
|
|
- Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys.
|
|
|
|
|
For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly.
|
|
|
|
|
Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).
|
|
|
|
|
- BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead!
|
|
|
|
|
- You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://dearimgui.org/controls_sheets
|
|
|
|
|
- If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo
|
|
|
|
|
to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
|
|
|
|
|
- If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing,
|
|
|
|
|
with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
|
|
|
|
|
- Mouse:
|
|
|
|
|
- PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
|
|
|
|
|
- PS4/PS5 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
|
|
|
|
|
- Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard.
|
|
|
|
|
- On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag.
|
|
|
|
|
Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
|
|
|
|
@ -379,7 +386,82 @@ CODE
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
- 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead.
|
|
|
|
|
- 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79.
|
|
|
|
|
- 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). See 1.61 changelog for details.
|
|
|
|
|
- 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete).
|
|
|
|
|
- ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl
|
|
|
|
|
- ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift
|
|
|
|
|
- ImGuiKey_ModAlt and ImGuiModFlags_Alt -> ImGuiMod_Alt
|
|
|
|
|
- ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super
|
|
|
|
|
the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends.
|
|
|
|
|
the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions.
|
|
|
|
|
exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they were not meant to be used anyway.
|
|
|
|
|
- 2022/09/20 (1.89) - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers.
|
|
|
|
|
this will require uses of legacy backend-dependent indices to be casted, e.g.
|
|
|
|
|
- with imgui_impl_glfw: IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A);
|
|
|
|
|
- with imgui_impl_win32: IsKeyPressed('A') -> IsKeyPressed((ImGuiKey)'A')
|
|
|
|
|
- etc. However if you are upgrading code you might well use the better, backend-agnostic IsKeyPressed(ImGuiKey_A) now!
|
|
|
|
|
- 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr);
|
|
|
|
|
- 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020):
|
|
|
|
|
- DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
|
|
|
|
|
- SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
|
|
|
|
|
- BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use BeginPopupContextWindow(const char*, ImGuiPopupFlags)
|
|
|
|
|
- 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries.
|
|
|
|
|
this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an item.
|
|
|
|
|
- previously this would make the window content size ~200x200:
|
|
|
|
|
Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();
|
|
|
|
|
- instead, please submit an item:
|
|
|
|
|
Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End();
|
|
|
|
|
- alternative:
|
|
|
|
|
Begin(...) + Dummy(ImVec2(200,200)) + End();
|
|
|
|
|
- content size is now only extended when submitting an item!
|
|
|
|
|
- with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert.
|
|
|
|
|
- without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it.
|
|
|
|
|
- 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete).
|
|
|
|
|
- added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter.
|
|
|
|
|
- old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), int frame_padding = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
|
|
|
|
|
- used the ImTextureID value to create an ID. This was inconsistent with other functions, led to ID conflicts, and caused problems with engines using transient ImTextureID values.
|
|
|
|
|
- had a FramePadding override which was inconsistent with other functions and made the already-long signature even longer.
|
|
|
|
|
- new signature: bool ImageButton(const char* str_id, ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
|
|
|
|
|
- requires an explicit identifier. You may still use e.g. PushID() calls and then pass an empty identifier.
|
|
|
|
|
- always uses style.FramePadding for padding, to be consistent with other buttons. You may use PushStyleVar() to alter this.
|
|
|
|
|
- 2022/07/08 (1.89) - inputs: removed io.NavInputs[] and ImGuiNavInput enum (following 1.87 changes).
|
|
|
|
|
- Official backends from 1.87+ -> no issue.
|
|
|
|
|
- Official backends from 1.60 to 1.86 -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need updating!
|
|
|
|
|
- Custom backends not writing to io.NavInputs[] -> no issue.
|
|
|
|
|
- Custom backends writing to io.NavInputs[] -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need fixing!
|
|
|
|
|
- TL;DR: Backends should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values instead of filling io.NavInput[].
|
|
|
|
|
- 2022/06/15 (1.88) - renamed IMGUI_DISABLE_METRICS_WINDOW to IMGUI_DISABLE_DEBUG_TOOLS for correctness. kept support for old define (will obsolete).
|
|
|
|
|
- 2022/05/03 (1.88) - backends: osx: removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture. All ImGui_ImplOSX_HandleEvent() calls should be removed as they are now unnecessary.
|
|
|
|
|
- 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API functions but technically present in imgui.h and ImGuiIO.
|
|
|
|
|
- 2022/01/20 (1.87) - inputs: reworded gamepad IO.
|
|
|
|
|
- Backend writing to io.NavInputs[] -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values.
|
|
|
|
|
- 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputing text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).
|
|
|
|
|
- 2022/01/17 (1.87) - inputs: reworked mouse IO.
|
|
|
|
|
- Backend writing to io.MousePos -> backend should call io.AddMousePosEvent()
|
|
|
|
|
- Backend writing to io.MouseDown[] -> backend should call io.AddMouseButtonEvent()
|
|
|
|
|
- Backend writing to io.MouseWheel -> backend should call io.AddMouseWheelEvent()
|
|
|
|
|
- Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only]
|
|
|
|
|
note: for all calls to IO new functions, the Dear ImGui context should be bound/current.
|
|
|
|
|
read https://github.com/ocornut/imgui/issues/4921 for details.
|
|
|
|
|
- 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.
|
|
|
|
|
- IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX)
|
|
|
|
|
- IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
|
|
|
|
|
- Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes).
|
|
|
|
|
- Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.*
|
|
|
|
|
- one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[] array. Will assert.
|
|
|
|
|
- inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper.
|
|
|
|
|
- 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum.
|
|
|
|
|
- 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed 'void* io.ImeWindowHandle' in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.
|
|
|
|
|
- 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete in 1.69, 1.70, 1.71, 1.72 (March-July 2019)
|
|
|
|
|
- ImGui::SetNextTreeNodeOpen() -> use ImGui::SetNextItemOpen()
|
|
|
|
|
- ImGui::GetContentRegionAvailWidth() -> use ImGui::GetContentRegionAvail().x
|
|
|
|
|
- ImGui::TreeAdvanceToLabelPos() -> use ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetTreeNodeToLabelSpacing());
|
|
|
|
|
- ImFontAtlas::CustomRect -> use ImFontAtlasCustomRect
|
|
|
|
|
- ImGuiColorEditFlags_RGB/HSV/HEX -> use ImGuiColorEditFlags_DisplayRGB/HSV/Hex
|
|
|
|
|
- 2021/12/20 (1.86) - backends: removed obsolete Marmalade backend (imgui_impl_marmalade.cpp) + example. Find last supported version at https://github.com/ocornut/imgui/wiki/Bindings
|
|
|
|
|
- 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. Please open an issue if you think you really need this function.
|
|
|
|
|
- 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is more useful.
|
|
|
|
|
- 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019):
|
|
|
|
|
- ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList()
|
|
|
|
|
- ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder
|
|
|
|
@ -722,10 +804,12 @@ CODE
|
|
|
|
|
Q&A: Usage
|
|
|
|
|
----------
|
|
|
|
|
|
|
|
|
|
Q: Why is my widget not reacting when I click on it?
|
|
|
|
|
Q: How can I have widgets with an empty label?
|
|
|
|
|
Q: How can I have multiple widgets with the same label?
|
|
|
|
|
Q: How can I display an image? What is ImTextureID, how does it works?
|
|
|
|
|
Q: About the ID Stack system..
|
|
|
|
|
- Why is my widget not reacting when I click on it?
|
|
|
|
|
- How can I have widgets with an empty label?
|
|
|
|
|
- How can I have multiple widgets with the same label?
|
|
|
|
|
- How can I have multiple windows with the same label?
|
|
|
|
|
Q: How can I display an image? What is ImTextureID, how does it work?
|
|
|
|
|
Q: How can I use my own math types instead of ImVec2/ImVec4?
|
|
|
|
|
Q: How can I interact with standard C++ types (such as std::string and std::vector)?
|
|
|
|
|
Q: How can I display custom shapes? (using low-level ImDrawList API)
|
|
|
|
@ -784,7 +868,6 @@ CODE
|
|
|
|
|
#include "imgui_internal.h"
|
|
|
|
|
|
|
|
|
|
// System includes
|
|
|
|
|
#include <ctype.h> // toupper
|
|
|
|
|
#include <stdio.h> // vsnprintf, sscanf, printf
|
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
|
|
|
|
|
#include <stddef.h> // intptr_t
|
|
|
|
@ -831,7 +914,7 @@ CODE
|
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
|
|
|
|
|
#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
|
|
|
|
|
#endif
|
|
|
|
|
#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
|
|
|
|
|
#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
|
|
|
|
|
#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).
|
|
|
|
|
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
|
|
|
|
|
#endif
|
|
|
|
@ -854,7 +937,7 @@ CODE
|
|
|
|
|
#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
|
|
|
|
|
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
|
|
|
|
|
#elif defined(__GNUC__)
|
|
|
|
|
// We disable -Wpragmas because GCC doesn't provide an has_warning equivalent and some forks/patches may not following the warning/version association.
|
|
|
|
|
// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association.
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
|
|
|
|
@ -878,7 +961,7 @@ static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time
|
|
|
|
|
// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend)
|
|
|
|
|
static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow().
|
|
|
|
|
static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time.
|
|
|
|
|
static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.
|
|
|
|
|
static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
// [SECTION] FORWARD DECLARATIONS
|
|
|
|
@ -902,7 +985,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSetti
|
|
|
|
|
// Platform Dependents default implementation for IO functions
|
|
|
|
|
static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
|
|
|
|
|
static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
|
|
|
|
|
static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
|
|
|
|
|
static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data);
|
|
|
|
|
|
|
|
|
|
namespace ImGui
|
|
|
|
|
{
|
|
|
|
@ -910,35 +993,45 @@ namespace ImGui
|
|
|
|
|
static void NavUpdate();
|
|
|
|
|
static void NavUpdateWindowing();
|
|
|
|
|
static void NavUpdateWindowingOverlay();
|
|
|
|
|
static void NavUpdateInitResult();
|
|
|
|
|
static void NavUpdateCancelRequest();
|
|
|
|
|
static void NavUpdateCreateMoveRequest();
|
|
|
|
|
static void NavUpdateCreateTabbingRequest();
|
|
|
|
|
static float NavUpdatePageUpPageDown();
|
|
|
|
|
static inline void NavUpdateAnyRequestFlag();
|
|
|
|
|
static void NavUpdateCreateWrappingRequest();
|
|
|
|
|
static void NavEndFrame();
|
|
|
|
|
static bool NavScoreItem(ImGuiNavItemData* result, ImRect cand);
|
|
|
|
|
static void NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel);
|
|
|
|
|
static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id);
|
|
|
|
|
static bool NavScoreItem(ImGuiNavItemData* result);
|
|
|
|
|
static void NavApplyItemToResult(ImGuiNavItemData* result);
|
|
|
|
|
static void NavProcessItem();
|
|
|
|
|
static void NavProcessItemForTabbingRequest(ImGuiID id);
|
|
|
|
|
static ImVec2 NavCalcPreferredRefPos();
|
|
|
|
|
static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);
|
|
|
|
|
static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
|
|
|
|
|
static void NavRestoreLayer(ImGuiNavLayer layer);
|
|
|
|
|
static void NavRestoreHighlightAfterMove();
|
|
|
|
|
static int FindWindowFocusIndex(ImGuiWindow* window);
|
|
|
|
|
|
|
|
|
|
// Error Checking
|
|
|
|
|
// Error Checking and Debug Tools
|
|
|
|
|
static void ErrorCheckNewFrameSanityChecks();
|
|
|
|
|
static void ErrorCheckEndFrameSanityChecks();
|
|
|
|
|
static void UpdateDebugToolItemPicker();
|
|
|
|
|
static void UpdateDebugToolStackQueries();
|
|
|
|
|
|
|
|
|
|
// Inputs
|
|
|
|
|
static void UpdateKeyboardInputs();
|
|
|
|
|
static void UpdateMouseInputs();
|
|
|
|
|
static void UpdateMouseWheel();
|
|
|
|
|
static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt);
|
|
|
|
|
|
|
|
|
|
// Misc
|
|
|
|
|
static void UpdateSettings();
|
|
|
|
|
static void UpdateMouseInputs();
|
|
|
|
|
static void UpdateMouseWheel();
|
|
|
|
|
static void UpdateTabFocus();
|
|
|
|
|
static void UpdateDebugToolItemPicker();
|
|
|
|
|
static bool UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect);
|
|
|
|
|
static void RenderWindowOuterBorders(ImGuiWindow* window);
|
|
|
|
|
static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size);
|
|
|
|
|
static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size);
|
|
|
|
|
static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open);
|
|
|
|
|
static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col);
|
|
|
|
|
static void RenderDimmedBackgrounds();
|
|
|
|
|
static ImGuiWindow* FindBlockingModal(ImGuiWindow* window);
|
|
|
|
|
|
|
|
|
|
// Viewports
|
|
|
|
|
static void UpdateViewportsNewFrame();
|
|
|
|
@ -1016,12 +1109,12 @@ ImGuiStyle::ImGuiStyle()
|
|
|
|
|
ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).
|
|
|
|
|
ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
|
|
|
|
|
ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
|
|
|
|
|
GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
|
|
|
|
|
GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar
|
|
|
|
|
GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
|
|
|
|
|
LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
|
|
|
|
|
TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
|
|
|
|
|
TabBorderSize = 0.0f; // Thickness of border around tabs.
|
|
|
|
|
TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
|
|
|
|
|
TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
|
|
|
|
|
ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
|
|
|
|
|
ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
|
|
|
|
|
SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
|
|
|
|
@ -1029,7 +1122,7 @@ ImGuiStyle::ImGuiStyle()
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
@ -1071,7 +1164,7 @@ ImGuiIO::ImGuiIO()
|
|
|
|
|
{
|
|
|
|
|
// Most fields are initialized with zero
|
|
|
|
|
memset(this, 0, sizeof(*this));
|
|
|
|
|
IM_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); // Our pre-C++11 IM_STATIC_ASSERT() macros triggers warning on modern compilers so we don't use it here.
|
|
|
|
|
IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT);
|
|
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
|
ConfigFlags = ImGuiConfigFlags_None;
|
|
|
|
@ -1083,10 +1176,14 @@ ImGuiIO::ImGuiIO()
|
|
|
|
|
LogFilename = "imgui_log.txt";
|
|
|
|
|
MouseDoubleClickTime = 0.30f;
|
|
|
|
|
MouseDoubleClickMaxDist = 6.0f;
|
|
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
|
|
|
|
|
for (int i = 0; i < ImGuiKey_COUNT; i++)
|
|
|
|
|
KeyMap[i] = -1;
|
|
|
|
|
#endif
|
|
|
|
|
KeyRepeatDelay = 0.275f;
|
|
|
|
|
KeyRepeatRate = 0.050f;
|
|
|
|
|
HoverDelayNormal = 0.30f;
|
|
|
|
|
HoverDelayShort = 0.10f;
|
|
|
|
|
UserData = NULL;
|
|
|
|
|
|
|
|
|
|
Fonts = NULL;
|
|
|
|
@ -1102,7 +1199,10 @@ ImGuiIO::ImGuiIO()
|
|
|
|
|
#else
|
|
|
|
|
ConfigMacOSXBehaviors = false;
|
|
|
|
|
#endif
|
|
|
|
|
ConfigInputTrickleEventQueue = true;
|
|
|
|
|
ConfigInputTextCursorBlink = true;
|
|
|
|
|
ConfigInputTextEnterKeepActive = false;
|
|
|
|
|
ConfigDragClickToInputText = false;
|
|
|
|
|
ConfigWindowsResizeFromEdges = true;
|
|
|
|
|
ConfigWindowsMoveFromTitleBarOnly = false;
|
|
|
|
|
ConfigMemoryCompactTimer = 60.0f;
|
|
|
|
@ -1113,38 +1213,48 @@ ImGuiIO::ImGuiIO()
|
|
|
|
|
GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
|
|
|
|
|
SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
|
|
|
|
|
ClipboardUserData = NULL;
|
|
|
|
|
ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
|
|
|
|
|
ImeWindowHandle = NULL;
|
|
|
|
|
SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl;
|
|
|
|
|
|
|
|
|
|
// Input (NB: we already have memset zero the entire structure!)
|
|
|
|
|
MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
|
|
|
MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
|
|
|
MouseDragThreshold = 6.0f;
|
|
|
|
|
for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
|
|
|
|
|
for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
|
|
|
|
|
for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
|
|
|
|
|
for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; }
|
|
|
|
|
AppAcceptingEvents = true;
|
|
|
|
|
BackendUsingLegacyKeyArrays = (ImS8)-1;
|
|
|
|
|
BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pass in translated ASCII characters for text input.
|
|
|
|
|
// - with glfw you can get those from the callback set in glfwSetCharCallback()
|
|
|
|
|
// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
|
|
|
|
|
// FIXME: Should in theory be called "AddCharacterEvent()" to be consistent with new API
|
|
|
|
|
void ImGuiIO::AddInputCharacter(unsigned int c)
|
|
|
|
|
{
|
|
|
|
|
if (c != 0)
|
|
|
|
|
InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID);
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
if (c == 0 || !AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_Text;
|
|
|
|
|
e.Source = ImGuiInputSource_Keyboard;
|
|
|
|
|
e.Text.Char = c;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so
|
|
|
|
|
// we should save the high surrogate.
|
|
|
|
|
void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
|
|
|
|
|
{
|
|
|
|
|
if (c == 0 && InputQueueSurrogate == 0)
|
|
|
|
|
if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ((c & 0xFC00) == 0xD800) // High surrogate, must save
|
|
|
|
|
{
|
|
|
|
|
if (InputQueueSurrogate != 0)
|
|
|
|
|
InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
|
|
|
|
|
AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
|
|
|
|
|
InputQueueSurrogate = c;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@ -1154,7 +1264,7 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
|
|
|
|
|
{
|
|
|
|
|
if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate
|
|
|
|
|
{
|
|
|
|
|
InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
|
|
|
|
|
AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -1167,39 +1277,230 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
|
|
|
|
|
|
|
|
|
|
InputQueueSurrogate = 0;
|
|
|
|
|
}
|
|
|
|
|
InputQueueCharacters.push_back(cp);
|
|
|
|
|
AddInputCharacter((unsigned)cp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
|
|
|
|
|
{
|
|
|
|
|
if (!AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
while (*utf8_chars != 0)
|
|
|
|
|
{
|
|
|
|
|
unsigned int c = 0;
|
|
|
|
|
utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL);
|
|
|
|
|
if (c != 0)
|
|
|
|
|
InputQueueCharacters.push_back((ImWchar)c);
|
|
|
|
|
AddInputCharacter(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: Perhaps we could clear queued events as well?
|
|
|
|
|
void ImGuiIO::ClearInputCharacters()
|
|
|
|
|
{
|
|
|
|
|
InputQueueCharacters.resize(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiIO::AddFocusEvent(bool focused)
|
|
|
|
|
// FIXME: Perhaps we could clear queued events as well?
|
|
|
|
|
void ImGuiIO::ClearInputKeys()
|
|
|
|
|
{
|
|
|
|
|
if (focused)
|
|
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
|
|
|
|
|
memset(KeysDown, 0, sizeof(KeysDown));
|
|
|
|
|
#endif
|
|
|
|
|
for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++)
|
|
|
|
|
{
|
|
|
|
|
KeysData[n].Down = false;
|
|
|
|
|
KeysData[n].DownDuration = -1.0f;
|
|
|
|
|
KeysData[n].DownDurationPrev = -1.0f;
|
|
|
|
|
}
|
|
|
|
|
KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
|
|
|
|
|
KeyMods = ImGuiMod_None;
|
|
|
|
|
MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
|
|
|
for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++)
|
|
|
|
|
{
|
|
|
|
|
MouseDown[n] = false;
|
|
|
|
|
MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f;
|
|
|
|
|
}
|
|
|
|
|
MouseWheel = MouseWheelH = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ImGuiInputEvent* FindLatestInputEvent(ImGuiInputEventType type, int arg = -1)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--)
|
|
|
|
|
{
|
|
|
|
|
ImGuiInputEvent* e = &g.InputEventsQueue[n];
|
|
|
|
|
if (e->Type != type)
|
|
|
|
|
continue;
|
|
|
|
|
if (type == ImGuiInputEventType_Key && e->Key.Key != arg)
|
|
|
|
|
continue;
|
|
|
|
|
if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg)
|
|
|
|
|
continue;
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Queue a new key down/up event.
|
|
|
|
|
// - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character)
|
|
|
|
|
// - bool down: Is the key down? use false to signify a key release.
|
|
|
|
|
// - float analog_value: 0.0f..1.0f
|
|
|
|
|
void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)
|
|
|
|
|
{
|
|
|
|
|
//if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }
|
|
|
|
|
if (key == ImGuiKey_None || !AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
IM_ASSERT(ImGui::IsNamedKeyOrModKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.
|
|
|
|
|
IM_ASSERT(!ImGui::IsAliasKey(key)); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events.
|
|
|
|
|
IM_ASSERT(key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself)
|
|
|
|
|
|
|
|
|
|
// Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data.
|
|
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
|
|
|
|
|
IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
|
|
|
|
|
if (BackendUsingLegacyKeyArrays == -1)
|
|
|
|
|
for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++)
|
|
|
|
|
IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
|
|
|
|
|
BackendUsingLegacyKeyArrays = 0;
|
|
|
|
|
#endif
|
|
|
|
|
if (ImGui::IsGamepadKey(key))
|
|
|
|
|
BackendUsingLegacyNavInputArray = false;
|
|
|
|
|
|
|
|
|
|
// Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed)
|
|
|
|
|
const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Key, (int)key);
|
|
|
|
|
const ImGuiKeyData* key_data = ImGui::GetKeyData(key);
|
|
|
|
|
const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down;
|
|
|
|
|
const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue;
|
|
|
|
|
if (latest_key_down == down && latest_key_analog == analog_value)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Clear buttons state when focus is lost
|
|
|
|
|
// (this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle)
|
|
|
|
|
memset(KeysDown, 0, sizeof(KeysDown));
|
|
|
|
|
for (int n = 0; n < IM_ARRAYSIZE(KeysDownDuration); n++)
|
|
|
|
|
KeysDownDuration[n] = KeysDownDurationPrev[n] = -1.0f;
|
|
|
|
|
KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
|
|
|
|
|
KeyMods = KeyModsPrev = ImGuiKeyModFlags_None;
|
|
|
|
|
for (int n = 0; n < IM_ARRAYSIZE(NavInputsDownDuration); n++)
|
|
|
|
|
NavInputsDownDuration[n] = NavInputsDownDurationPrev[n] = -1.0f;
|
|
|
|
|
// Add event
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_Key;
|
|
|
|
|
e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad : ImGuiInputSource_Keyboard;
|
|
|
|
|
e.Key.Key = key;
|
|
|
|
|
e.Key.Down = down;
|
|
|
|
|
e.Key.AnalogValue = analog_value;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down)
|
|
|
|
|
{
|
|
|
|
|
if (!AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
AddKeyAnalogEvent(key, down, down ? 1.0f : 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [Optional] Call after AddKeyEvent().
|
|
|
|
|
// Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices.
|
|
|
|
|
// If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can avoid calling this.
|
|
|
|
|
void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index)
|
|
|
|
|
{
|
|
|
|
|
if (key == ImGuiKey_None)
|
|
|
|
|
return;
|
|
|
|
|
IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512
|
|
|
|
|
IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511
|
|
|
|
|
IM_UNUSED(native_keycode); // Yet unused
|
|
|
|
|
IM_UNUSED(native_scancode); // Yet unused
|
|
|
|
|
|
|
|
|
|
// Build native->imgui map so old user code can still call key functions with native 0..511 values.
|
|
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
|
|
|
|
|
const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode;
|
|
|
|
|
if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key))
|
|
|
|
|
return;
|
|
|
|
|
KeyMap[legacy_key] = key;
|
|
|
|
|
KeyMap[key] = legacy_key;
|
|
|
|
|
#else
|
|
|
|
|
IM_UNUSED(key);
|
|
|
|
|
IM_UNUSED(native_legacy_index);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen.
|
|
|
|
|
void ImGuiIO::SetAppAcceptingEvents(bool accepting_events)
|
|
|
|
|
{
|
|
|
|
|
AppAcceptingEvents = accepting_events;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Queue a mouse move event
|
|
|
|
|
void ImGuiIO::AddMousePosEvent(float x, float y)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
if (!AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Apply same flooring as UpdateMouseInputs()
|
|
|
|
|
ImVec2 pos((x > -FLT_MAX) ? ImFloorSigned(x) : x, (y > -FLT_MAX) ? ImFloorSigned(y) : y);
|
|
|
|
|
|
|
|
|
|
// Filter duplicate
|
|
|
|
|
const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MousePos);
|
|
|
|
|
const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos;
|
|
|
|
|
if (latest_pos.x == pos.x && latest_pos.y == pos.y)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_MousePos;
|
|
|
|
|
e.Source = ImGuiInputSource_Mouse;
|
|
|
|
|
e.MousePos.PosX = pos.x;
|
|
|
|
|
e.MousePos.PosY = pos.y;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
|
|
|
|
|
if (!AppAcceptingEvents)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Filter duplicate
|
|
|
|
|
const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MouseButton, (int)mouse_button);
|
|
|
|
|
const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button];
|
|
|
|
|
if (latest_button_down == down)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_MouseButton;
|
|
|
|
|
e.Source = ImGuiInputSource_Mouse;
|
|
|
|
|
e.MouseButton.Button = mouse_button;
|
|
|
|
|
e.MouseButton.Down = down;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Queue a mouse wheel event (most mouse/API will only have a Y component)
|
|
|
|
|
void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
|
|
|
|
|
// Filter duplicate (unlike most events, wheel values are relative and easy to filter)
|
|
|
|
|
if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_MouseWheel;
|
|
|
|
|
e.Source = ImGuiInputSource_Mouse;
|
|
|
|
|
e.MouseWheel.WheelX = wheel_x;
|
|
|
|
|
e.MouseWheel.WheelY = wheel_y;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiIO::AddFocusEvent(bool focused)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
IM_ASSERT(&g.IO == this && "Can only add events to current context.");
|
|
|
|
|
|
|
|
|
|
// Filter duplicate
|
|
|
|
|
const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Focus);
|
|
|
|
|
const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost;
|
|
|
|
|
if (latest_focused == focused)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ImGuiInputEvent e;
|
|
|
|
|
e.Type = ImGuiInputEventType_Focus;
|
|
|
|
|
e.AppFocused.Focused = focused;
|
|
|
|
|
g.InputEventsQueue.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
@ -1330,14 +1631,14 @@ ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c,
|
|
|
|
|
int ImStricmp(const char* str1, const char* str2)
|
|
|
|
|
{
|
|
|
|
|
int d;
|
|
|
|
|
while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
|
|
|
|
|
while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; }
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ImStrnicmp(const char* str1, const char* str2, size_t count)
|
|
|
|
|
{
|
|
|
|
|
int d = 0;
|
|
|
|
|
while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
|
|
|
|
|
while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1404,14 +1705,14 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char
|
|
|
|
|
if (!needle_end)
|
|
|
|
|
needle_end = needle + strlen(needle);
|
|
|
|
|
|
|
|
|
|
const char un0 = (char)toupper(*needle);
|
|
|
|
|
const char un0 = (char)ImToUpper(*needle);
|
|
|
|
|
while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
|
|
|
|
|
{
|
|
|
|
|
if (toupper(*haystack) == un0)
|
|
|
|
|
if (ImToUpper(*haystack) == un0)
|
|
|
|
|
{
|
|
|
|
|
const char* b = needle + 1;
|
|
|
|
|
for (const char* a = haystack + 1; b < needle_end; a++, b++)
|
|
|
|
|
if (toupper(*a) != toupper(*b))
|
|
|
|
|
if (ImToUpper(*a) != ImToUpper(*b))
|
|
|
|
|
break;
|
|
|
|
|
if (b == needle_end)
|
|
|
|
|
return haystack;
|
|
|
|
@ -1455,8 +1756,12 @@ const char* ImStrSkipBlank(const char* str)
|
|
|
|
|
// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)
|
|
|
|
|
#ifdef IMGUI_USE_STB_SPRINTF
|
|
|
|
|
#define STB_SPRINTF_IMPLEMENTATION
|
|
|
|
|
#ifdef IMGUI_STB_SPRINTF_FILENAME
|
|
|
|
|
#include IMGUI_STB_SPRINTF_FILENAME
|
|
|
|
|
#else
|
|
|
|
|
#include "stb_sprintf.h"
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(_MSC_VER) && !defined(vsnprintf)
|
|
|
|
|
#define vsnprintf _vsnprintf
|
|
|
|
@ -1496,6 +1801,25 @@ int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
|
|
|
|
|
}
|
|
|
|
|
#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
|
|
|
|
|
|
|
|
|
|
void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args);
|
|
|
|
|
*out_buf = g.TempBuffer.Data;
|
|
|
|
|
if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }
|
|
|
|
|
va_end(args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args);
|
|
|
|
|
*out_buf = g.TempBuffer.Data;
|
|
|
|
|
if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CRC32 needs a 1KB lookup table (not cache friendly)
|
|
|
|
|
// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily:
|
|
|
|
|
// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe.
|
|
|
|
@ -1937,7 +2261,7 @@ void ImGuiStorage::BuildSortByKey()
|
|
|
|
|
{
|
|
|
|
|
struct StaticFunc
|
|
|
|
|
{
|
|
|
|
|
static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
|
|
|
|
|
static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs)
|
|
|
|
|
{
|
|
|
|
|
// We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
|
|
|
|
|
if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) return +1;
|
|
|
|
@ -1945,8 +2269,7 @@ void ImGuiStorage::BuildSortByKey()
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (Data.Size > 1)
|
|
|
|
|
ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairCompareByID);
|
|
|
|
|
ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairComparerByID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
|
|
|
|
@ -2058,18 +2381,15 @@ void ImGuiStorage::SetAllInt(int v)
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
|
|
|
|
|
ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
|
|
|
|
|
ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077
|
|
|
|
|
{
|
|
|
|
|
InputBuf[0] = 0;
|
|
|
|
|
CountGrep = 0;
|
|
|
|
|
if (default_filter)
|
|
|
|
|
{
|
|
|
|
|
ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
|
|
|
|
|
Build();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
InputBuf[0] = 0;
|
|
|
|
|
CountGrep = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ImGuiTextFilter::Draw(const char* label, float width)
|
|
|
|
@ -2156,7 +2476,7 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// [SECTION] ImGuiTextBuffer
|
|
|
|
|
// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// On some platform vsnprintf() takes va_list by reference and modifies it.
|
|
|
|
@ -2224,6 +2544,20 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
|
|
|
|
|
va_end(args_copy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
|
|
|
|
|
{
|
|
|
|
|
IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
|
|
|
|
|
if (old_size == new_size)
|
|
|
|
|
return;
|
|
|
|
|
if (EndOffset == 0 || base[EndOffset - 1] == '\n')
|
|
|
|
|
LineOffsets.push_back(EndOffset);
|
|
|
|
|
const char* base_end = base + new_size;
|
|
|
|
|
for (const char* p = base + old_size; (p = (const char*)memchr(p, '\n', base_end - p)) != 0; )
|
|
|
|
|
if (++p < base_end) // Don't push a trailing offset on last \n
|
|
|
|
|
LineOffsets.push_back((int)(intptr_t)(p - base));
|
|
|
|
|
EndOffset = ImMax(EndOffset, new_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// [SECTION] ImGuiListClipper
|
|
|
|
|
// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed
|
|
|
|
@ -2238,9 +2572,10 @@ static bool GetSkipItemForListClipping()
|
|
|
|
|
return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper to calculate coarse clipping of large list of evenly sized items.
|
|
|
|
|
// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
|
|
|
|
|
// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
|
|
|
|
|
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
|
|
|
|
// Legacy helper to calculate coarse clipping of large list of evenly sized items.
|
|
|
|
|
// This legacy API is not ideal because it assumes we will return a single contiguous rectangle.
|
|
|
|
|
// Prefer using ImGuiListClipper which can returns non-contiguous ranges.
|
|
|
|
|
void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
@ -2259,20 +2594,23 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect
|
|
|
|
|
ImRect unclipped_rect = window->ClipRect;
|
|
|
|
|
// We don't include g.NavId's rectangle in there (unless g.NavJustMovedToId is set) because the rectangle enlargement can get costly.
|
|
|
|
|
ImRect rect = window->ClipRect;
|
|
|
|
|
if (g.NavMoveScoringItems)
|
|
|
|
|
unclipped_rect.Add(g.NavScoringRect);
|
|
|
|
|
rect.Add(g.NavScoringNoClipRect);
|
|
|
|
|
if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId)
|
|
|
|
|
unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max)); // Could store and use NavJustMovedToRectRel
|
|
|
|
|
rect.Add(WindowRectRelToAbs(window, window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel
|
|
|
|
|
|
|
|
|
|
const ImVec2 pos = window->DC.CursorPos;
|
|
|
|
|
int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
|
|
|
|
|
int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
|
|
|
|
|
int start = (int)((rect.Min.y - pos.y) / items_height);
|
|
|
|
|
int end = (int)((rect.Max.y - pos.y) / items_height);
|
|
|
|
|
|
|
|
|
|
// When performing a navigation request, ensure we have one item extra in the direction we are moving to
|
|
|
|
|
if (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Up)
|
|
|
|
|
// FIXME: Verify this works with tabbing
|
|
|
|
|
const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
|
|
|
|
|
if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up)
|
|
|
|
|
start--;
|
|
|
|
|
if (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Down)
|
|
|
|
|
if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down)
|
|
|
|
|
end++;
|
|
|
|
|
|
|
|
|
|
start = ImClamp(start, 0, items_count);
|
|
|
|
@ -2280,17 +2618,42 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items
|
|
|
|
|
*out_items_display_start = start;
|
|
|
|
|
*out_items_display_end = end;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height)
|
|
|
|
|
static void ImGuiListClipper_SortAndFuseRanges(ImVector<ImGuiListClipperRange>& ranges, int offset = 0)
|
|
|
|
|
{
|
|
|
|
|
if (ranges.Size - offset <= 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3 entries)
|
|
|
|
|
for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end)
|
|
|
|
|
for (int i = offset; i < sort_end + offset; ++i)
|
|
|
|
|
if (ranges[i].Min > ranges[i + 1].Min)
|
|
|
|
|
ImSwap(ranges[i], ranges[i + 1]);
|
|
|
|
|
|
|
|
|
|
// Now fuse ranges together as much as possible.
|
|
|
|
|
for (int i = 1 + offset; i < ranges.Size; i++)
|
|
|
|
|
{
|
|
|
|
|
IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert);
|
|
|
|
|
if (ranges[i - 1].Max < ranges[i].Min)
|
|
|
|
|
continue;
|
|
|
|
|
ranges[i - 1].Min = ImMin(ranges[i - 1].Min, ranges[i].Min);
|
|
|
|
|
ranges[i - 1].Max = ImMax(ranges[i - 1].Max, ranges[i].Max);
|
|
|
|
|
ranges.erase(ranges.Data + i);
|
|
|
|
|
i--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height)
|
|
|
|
|
{
|
|
|
|
|
// Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor.
|
|
|
|
|
// FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
|
|
|
|
|
// The clipper should probably have a 4th step to display the last item in a regular manner.
|
|
|
|
|
// The clipper should probably have a final step to display the last item in a regular manner, maybe with an opt-out flag for data sets which may have costly seek?
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
|
float off_y = pos_y - window->DC.CursorPos.y;
|
|
|
|
|
window->DC.CursorPos.y = pos_y;
|
|
|
|
|
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y);
|
|
|
|
|
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y);
|
|
|
|
|
window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage.
|
|
|
|
|
window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list.
|
|
|
|
|
if (ImGuiOldColumns* columns = window->DC.CurrentColumns)
|
|
|
|
@ -2306,6 +2669,15 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ImGuiListClipper_SeekCursorForItem(ImGuiListClipper* clipper, int item_n)
|
|
|
|
|
{
|
|
|
|
|
// StartPosY starts from ItemsFrozen hence the subtraction
|
|
|
|
|
// Perform the add and multiply with double to allow seeking through larger ranges
|
|
|
|
|
ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData;
|
|
|
|
|
float pos_y = (float)((double)clipper->StartPosY + data->LossynessOffset + (double)(item_n - data->ItemsFrozen) * clipper->ItemsHeight);
|
|
|
|
|
ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, clipper->ItemsHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGuiListClipper::ImGuiListClipper()
|
|
|
|
|
{
|
|
|
|
|
memset(this, 0, sizeof(*this));
|
|
|
|
@ -2314,16 +2686,14 @@ ImGuiListClipper::ImGuiListClipper()
|
|
|
|
|
|
|
|
|
|
ImGuiListClipper::~ImGuiListClipper()
|
|
|
|
|
{
|
|
|
|
|
IM_ASSERT(ItemsCount == -1 && "Forgot to call End(), or to Step() until false?");
|
|
|
|
|
End();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1
|
|
|
|
|
// Use case B: Begin() called from constructor with items_height>0
|
|
|
|
|
// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
|
|
|
|
|
void ImGuiListClipper::Begin(int items_count, float items_height)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
|
IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin(%d,%.2f) in '%s'\n", items_count, items_height, window->Name);
|
|
|
|
|
|
|
|
|
|
if (ImGuiTable* table = g.CurrentTable)
|
|
|
|
|
if (table->IsInsideRow)
|
|
|
|
@ -2332,127 +2702,196 @@ void ImGuiListClipper::Begin(int items_count, float items_height)
|
|
|
|
|
StartPosY = window->DC.CursorPos.y;
|
|
|
|
|
ItemsHeight = items_height;
|
|
|
|
|
ItemsCount = items_count;
|
|
|
|
|
ItemsFrozen = 0;
|
|
|
|
|
StepNo = 0;
|
|
|
|
|
DisplayStart = -1;
|
|
|
|
|
DisplayEnd = 0;
|
|
|
|
|
|
|
|
|
|
// Acquire temporary buffer
|
|
|
|
|
if (++g.ClipperTempDataStacked > g.ClipperTempData.Size)
|
|
|
|
|
g.ClipperTempData.resize(g.ClipperTempDataStacked, ImGuiListClipperData());
|
|
|
|
|
ImGuiListClipperData* data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
|
|
|
|
|
data->Reset(this);
|
|
|
|
|
data->LossynessOffset = window->DC.CursorStartPosLossyness.y;
|
|
|
|
|
TempData = data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImGuiListClipper::End()
|
|
|
|
|
{
|
|
|
|
|
if (ItemsCount < 0) // Already ended
|
|
|
|
|
return;
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData)
|
|
|
|
|
{
|
|
|
|
|
// In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user.
|
|
|
|
|
IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name);
|
|
|
|
|
if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0)
|
|
|
|
|
ImGuiListClipper_SeekCursorForItem(this, ItemsCount);
|
|
|
|
|
|
|
|
|
|
// In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user.
|
|
|
|
|
if (ItemsCount < INT_MAX && DisplayStart >= 0)
|
|
|
|
|
SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight);
|
|
|
|
|
// Restore temporary buffer and fix back pointers which may be invalidated when nesting
|
|
|
|
|
IM_ASSERT(data->ListClipper == this);
|
|
|
|
|
data->StepNo = data->Ranges.Size;
|
|
|
|
|
if (--g.ClipperTempDataStacked > 0)
|
|
|
|
|
{
|
|
|
|
|
data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
|
|
|
|
|
data->ListClipper->TempData = data;
|
|
|
|
|
}
|
|
|
|
|
TempData = NULL;
|
|
|
|
|
}
|
|
|
|
|
ItemsCount = -1;
|
|
|
|
|
StepNo = 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ImGuiListClipper::Step()
|
|
|
|
|
void ImGuiListClipper::ForceDisplayRangeByIndices(int item_min, int item_max)
|
|
|
|
|
{
|
|
|
|
|
ImGuiListClipperData* data = (ImGuiListClipperData*)TempData;
|
|
|
|
|
IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet.
|
|
|
|
|
IM_ASSERT(item_min <= item_max);
|
|
|
|
|
if (item_min < item_max)
|
|
|
|
|
data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_min, item_max));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
|
|
|
|
|
{
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
|
ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData;
|
|
|
|
|
IM_ASSERT(data != NULL && "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?");
|
|
|
|
|
|
|
|
|
|
ImGuiTable* table = g.CurrentTable;
|
|
|
|
|
if (table && table->IsInsideRow)
|
|
|
|
|
ImGui::TableEndRow(table);
|
|
|
|
|
|
|
|
|
|
// No items
|
|
|
|
|
if (ItemsCount == 0 || GetSkipItemForListClipping())
|
|
|
|
|
{
|
|
|
|
|
End();
|
|
|
|
|
if (clipper->ItemsCount == 0 || GetSkipItemForListClipping())
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height)
|
|
|
|
|
if (StepNo == 0)
|
|
|
|
|
// While we are in frozen row state, keep displaying items one by one, unclipped
|
|
|
|
|
// FIXME: Could be stored as a table-agnostic state.
|
|
|
|
|
if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows)
|
|
|
|
|
{
|
|
|
|
|
// While we are in frozen row state, keep displaying items one by one, unclipped
|
|
|
|
|
// FIXME: Could be stored as a table-agnostic state.
|
|
|
|
|
if (table != NULL && !table->IsUnfrozenRows)
|
|
|
|
|
{
|
|
|
|
|
DisplayStart = ItemsFrozen;
|
|
|
|
|
DisplayEnd = ItemsFrozen + 1;
|
|
|
|
|
ItemsFrozen++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StartPosY = window->DC.CursorPos.y;
|
|
|
|
|
if (ItemsHeight <= 0.0f)
|
|
|
|
|
{
|
|
|
|
|
// Submit the first item so we can measure its height (generally it is 0..1)
|
|
|
|
|
DisplayStart = ItemsFrozen;
|
|
|
|
|
DisplayEnd = ItemsFrozen + 1;
|
|
|
|
|
StepNo = 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Already has item height (given by user in Begin): skip to calculating step
|
|
|
|
|
DisplayStart = DisplayEnd;
|
|
|
|
|
StepNo = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 1: the clipper infer height from first element
|
|
|
|
|
if (StepNo == 1)
|
|
|
|
|
{
|
|
|
|
|
IM_ASSERT(ItemsHeight <= 0.0f);
|
|
|
|
|
if (table)
|
|
|
|
|
{
|
|
|
|
|
const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row
|
|
|
|
|
const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell.
|
|
|
|
|
ItemsHeight = pos_y2 - pos_y1;
|
|
|
|
|
window->DC.CursorPos.y = pos_y2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ItemsHeight = window->DC.CursorPos.y - StartPosY;
|
|
|
|
|
}
|
|
|
|
|
IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!");
|
|
|
|
|
StepNo = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reached end of list
|
|
|
|
|
if (DisplayEnd >= ItemsCount)
|
|
|
|
|
{
|
|