From 97fad073d942ab06083d65b5961e583bc033d528 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Fri, 10 Nov 2017 03:01:56 +0100 Subject: [PATCH] Update imgui to 1.52. --- imgui/LICENSE.txt | 2 +- imgui/imconfig.h | 13 +- imgui/imgui.cpp | 2151 ++++++++++++++++++++++++---------------- imgui/imgui.h | 353 ++++--- imgui/imgui_demo.cpp | 185 ++-- imgui/imgui_draw.cpp | 409 +++++--- imgui/imgui_internal.h | 173 ++-- 7 files changed, 2024 insertions(+), 1262 deletions(-) diff --git a/imgui/LICENSE.txt b/imgui/LICENSE.txt index b28ef225..5a9b98b8 100644 --- a/imgui/LICENSE.txt +++ b/imgui/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2015 Omar Cornut and ImGui contributors +Copyright (c) 2014-2017 Omar Cornut and ImGui contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/imgui/imconfig.h b/imgui/imconfig.h index 894f24e4..75b7ee58 100644 --- a/imgui/imconfig.h +++ b/imgui/imconfig.h @@ -13,24 +13,27 @@ //#define IMGUI_API __declspec( dllexport ) //#define IMGUI_API __declspec( dllimport ) +//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce like hood of using already obsolete function/names +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //---- Include imgui_user.h at the end of imgui.h //#define IMGUI_INCLUDE_IMGUI_USER_H //---- Don't implement default handlers for Windows (so as not to link with OpenClipboard() and others Win32 functions) -//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS -//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS //---- Don't implement test window functionality (ShowTestWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) //---- It is very strongly recommended to NOT disable the test windows. Please read the comment at the top of imgui_demo.cpp to learn why. //#define IMGUI_DISABLE_TEST_WINDOWS -//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce like hood of using already obsolete function/names -//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS +//---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS //---- Pack colors to BGRA instead of RGBA (remove need to post process vertex buffer in back ends) //#define IMGUI_USE_BGRA_PACKED_COLOR -//---- Implement STB libraries in a namespace to avoid conflicts +//---- Implement STB libraries in a namespace to avoid linkage conflicts //#define IMGUI_STB_NAMESPACE ImGuiStb //---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. diff --git a/imgui/imgui.cpp b/imgui/imgui.cpp index 26ed1826..b15c059f 100644 --- a/imgui/imgui.cpp +++ b/imgui/imgui.cpp @@ -1,11 +1,11 @@ -// dear imgui, v1.51 +// dear imgui, v1.52 // (main code and documentation) // See ImGui::ShowTestWindow() in imgui_demo.cpp for demo code. -// Newcomers, read 'Programmer guide' below for notes on how to setup ImGui in your codebase. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. // Get latest version at https://github.com/ocornut/imgui // Releases change-log at https://github.com/ocornut/imgui/releases -// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/772 +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // This library is free but I need your support to sustain development and maintenance. // If you work for a company, please consider financial support, e.g: https://www.patreon.com/imgui @@ -17,22 +17,22 @@ - END-USER GUIDE - PROGRAMMER GUIDE (read me!) - Read first - - How to update to a newer version of ImGui - - Getting started with integrating ImGui in your code/engine + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine - API BREAKING CHANGES (read me when you update!) - ISSUES & TODO LIST - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS - How can I help? - What is ImTextureID and how do I display an image? - - I integrated ImGui in my engine and the text or lines are blurry.. - - I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels/IDs. - - How can I tell when ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? - How can I load a different font than the default? - How can I easily use icons in my application? - How can I load multiple fonts? - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? - - How can I preserve my ImGui context across reloading a DLL? (loss of the global/static variables) + - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) - ISSUES & TODO-LIST - CODE @@ -48,7 +48,8 @@ - Minimize setup and maintenance - Minimize state storage on user side - Portable, minimize dependencies, run on target (consoles, phones, etc.) - - Efficient runtime and memory consumption (NB- we do allocate when "growing" content - creating a window / opening a tree node for the first time, etc. - but a typical frame won't allocate anything) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node + for the first time, etc. but a typical frame won't allocate anything) Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: - Doesn't look fancy, doesn't animate @@ -85,11 +86,12 @@ READ FIRST - Read the FAQ below this section! - - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention on your side, no state duplication, less sync, less bugs. + - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention + on your side, no state duplication, less sync, less bugs. - Call and read ImGui::ShowTestWindow() for demo code demonstrating most features. - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 - HOW TO UPDATE TO A NEWER VERSION OF IMGUI + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. @@ -98,27 +100,30 @@ Please report any issue to the GitHub page! - Try to keep your copy of dear imgui reasonably up to date. - GETTING STARTED WITH INTEGRATING IMGUI IN YOUR CODE/ENGINE + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE - - Add the ImGui source files to your projects, using your preferred build system. It is recommended you build the .cpp files as part of your project and not as a library. + - Add the Dear ImGui source files to your projects, using your preferred build system. + It is recommended you build the .cpp files as part of your project and not as a library. - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. - - See examples/ folder for standalone sample applications. To understand the integration process, you can read examples/opengl2_example/ because it is short, - then switch to the one more appropriate to your use case. + - See examples/ folder for standalone sample applications. To understand the integration process, you can read examples/opengl2_example/ because + it is short, then switch to the one more appropriate to your use case. - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/. - - When using ImGui, your programming IDE if your friend: follow the declaration of variables, functions and types to find comments about them. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. - - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize (application resolution). - Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic integration you don't need to worry about it all. + - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize + (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic + integration you don't need to worry about it all. - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory. - Every frame: - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.) - - Call ImGui::NewFrame() to begin the imgui frame + - Call ImGui::NewFrame() to begin the frame - You can use any ImGui function you want between NewFrame() and Render() - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler. - (if you don't need to render, you still need to call Render() and ignore the callback, or call EndFrame() instead. if you call neither some aspects of windows focusing/moving will appear broken.) + (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break) - All rendering information are stored into command-lists until ImGui::Render() is called. - - ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. - - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases of your own application. + - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. + - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases + of your own application. - Refer to the examples applications in the examples/ folder for instruction on how to setup your code. - A minimal application skeleton may be: @@ -126,7 +131,7 @@ ImGuiIO& io = ImGui::GetIO(); io.DisplaySize.x = 1920.0f; io.DisplaySize.y = 1280.0f; - io.RenderDrawListsFn = MyRenderFunction; // Setup a render function, or set to NULL and call GetDrawData() after Render() to access the render data. + io.RenderDrawListsFn = MyRenderFunction; // Setup a render function, or set to NULL and call GetDrawData() after Render() to access render data. // TODO: Fill others settings of the io structure later. // Load texture atlas (there is a default font so you don't need to care about choosing a font yet) @@ -180,9 +185,16 @@ } else { - // Render 'pcmd->ElemCount/3' texture triangles + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. MyEngineBindTexture(pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API supports it. + // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later. MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); } idx_buffer += pcmd->ElemCount; @@ -204,7 +216,15 @@ Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. Also read releases logs https://github.com/ocornut/imgui/releases for more details. - - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. @@ -337,40 +357,41 @@ ====================================== Q: How can I help? - A: - If you are experienced enough with ImGui and with C/C++, look at the todo list and see how you want/can help! + A: - If you are experienced enough with Dear ImGui and with C/C++, look at the todo list and see how you want/can help! - Become a Patron/donate! Convince your company to become a Patron or provide serious funding for development time! See http://www.patreon.com/imgui Q: What is ImTextureID and how do I display an image? A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. - ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! + Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc. At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render. Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!) To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. - ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. + Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. It is your responsibility to get textures uploaded to your GPU. - Q: I integrated ImGui in my engine and the text or lines are blurry.. + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. - Q: I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around.. - A: Most likely you are mishandling the clipping rectangles in your render function. Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). Q: Can I have multiple widgets with the same label? Can I have widget without a label? - A: Yes. A primer on the use of labels/IDs in ImGui.. + A: Yes. A primer on the use of labels/IDs in Dear ImGui.. - Elements that are not clickable, such as Text() items don't need an ID. - - Interactive widgets require state to be carried over multiple frames (most typically ImGui often needs to remember what is the "active" widget). - to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. + - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is + the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. Button("OK"); // Label = "OK", ID = hash of "OK" Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" - - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" in two different windows - or in two different locations of a tree. + - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" + in two different windows or in two different locations of a tree. - If you have a same ID twice in the same location, you'll have a conflict: @@ -379,8 +400,8 @@ Fear not! this is easy to solve and there are many ways to solve it! - - When passing a label you can optionally specify extra unique ID information within string itself. This helps solving the simpler collision cases. - use "##" to pass a complement to the ID that won't be visible to the end-user: + - When passing a label you can optionally specify extra unique ID information within string itself. + This helps solving the simpler collision cases. Use "##" to pass a complement to the ID that won't be visible to the end-user: Button("Play"); // Label = "Play", ID = hash of "Play" Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) @@ -448,18 +469,22 @@ - When working with trees, ID are used to preserve the open/close state of each tree node. Depending on your use cases you may want to use strings, indices or pointers as ID. - e.g. when displaying a single object that may change over time (1-1 relationship), using a static string as ID will preserve your node open/closed state when the targeted object change. - e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. experiment and see what makes more sense! + e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your + node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. + experiment and see what makes more sense! - Q: How can I tell when ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure. - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. - - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console without a keyboard). - Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is also generally ok, - as the bool toggles fairly rarely and you don't generally expect to interact with either ImGui or your application during the same frame when that transition occurs. - ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. - (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically have 'io.WantCaptureKeyboard=false'. - Depending on your application logic it may or not be inconvenient. You might want to track which key-downs were for ImGui (e.g. with an array of bool) and filter out the corresponding key-ups.) + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is + also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during + the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, + so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. + (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) A: Use the font atlas to load the TTF/OTF file you want: @@ -469,8 +494,8 @@ io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() Q: How can I easily use icons in my application? - A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your strings. - Read 'How can I load multiple fonts?' and the file 'extra_fonts/README.txt' for instructions. + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your + strings. Read 'How can I load multiple fonts?' and the file 'extra_fonts/README.txt' for instructions and useful header files. Q: How can I load multiple fonts? A: Use the font atlas to pack them into a single texture: @@ -520,19 +545,24 @@ Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that. - For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. + For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. + The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. - Q: How can I preserve my ImGui context across reloading a DLL? (loss of the global/static variables) - A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' so you don't rely on the default globals. + Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' + so you don't rely on the default globals. Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) - A: The easiest way is to create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, zero background alpha, - then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. - You can also perfectly create a standalone ImDrawList instance _but_ you need ImGui to be initialized because ImDrawList pulls from ImGui data to retrieve the coordinates of the white pixel. + A: The easiest way is to create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, + zero background alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + You can also perfectly create a standalone ImDrawList instance _but_ you need ImGui to be initialized because ImDrawList pulls from ImGui + data to retrieve the coordinates of the white pixel. - - tip: the construct 'IMGUI_ONCE_UPON_A_FRAME { ... }' will run the block of code only once a frame. You can use it to quickly add custom UI in the middle of a deep nested inner loop in your code. - - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug" - - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. - tip: you can call Render() multiple times (e.g for VR renders). - tip: call and read the ShowTestWindow() code in imgui_demo.cpp for more example of how to use ImGui! @@ -582,16 +612,12 @@ #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wsuggest-attribute=format" #endif //------------------------------------------------------------------------- // Forward Declarations //------------------------------------------------------------------------- -static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* text_end = NULL); - -static void PushMultiItemsWidths(int components, float w_full = 0.0f); static float GetDraggedColumnOffset(int column_index); static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); @@ -603,12 +629,11 @@ static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); -static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs); +static ImGuiWindow* FindHoveredWindow(ImVec2 pos); static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); -static inline bool IsWindowContentHoverable(ImGuiWindow* window); static void ClearSetNextWindowData(); static void CheckStacksSize(ImGuiWindow* window, bool write); -static void Scrollbar(ImGuiWindow* window, bool horizontal); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); static void AddDrawListToRenderList(ImVector& out_render_list, ImDrawList* draw_list); static void AddWindowToRenderList(ImVector& out_render_list, ImGuiWindow* window); @@ -622,10 +647,8 @@ static void MarkIniSettingsDirty(ImGuiWindow* window); static ImRect GetVisibleRect(); -static bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); -static void CloseInactivePopups(); +static void CloseInactivePopups(ImGuiWindow* ref_window); static void ClosePopupToLevel(int remaining); -static void ClosePopup(ImGuiID id); static ImGuiWindow* GetFrontMostModalRootWindow(); static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& rect_to_avoid); @@ -638,6 +661,11 @@ static inline void DataTypeFormatString(ImGuiDataType data_type, void* data static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); +namespace ImGui +{ +static void FocusPreviousWindow(); +} + //----------------------------------------------------------------------------- // Platform dependent default implementations //----------------------------------------------------------------------------- @@ -695,49 +723,80 @@ ImGuiStyle::ImGuiStyle() AntiAliasedShapes = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) CurveTessellationTol = 1.25f; // Tessellation tolerance. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - Colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - Colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); - Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - Colors[ImGuiCol_PopupBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); - Colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.40f); - Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); // Background of checkbox, radio button, plot, slider, text input - Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f); - Colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f); - Colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); - Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); - Colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); - Colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); - Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); - Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); - Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); - Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); - Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); - Colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); - Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); - Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); - Colors[ImGuiCol_Button] = ImVec4(0.67f, 0.40f, 0.40f, 0.60f); - Colors[ImGuiCol_ButtonHovered] = ImVec4(0.67f, 0.40f, 0.40f, 1.00f); - Colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); - Colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); - Colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); - Colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); - Colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); - Colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); - Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); - Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); - Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f); - Colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); - Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); - Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); - Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 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_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); - Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + ImGui::StyleColorsClassic(this); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); + colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.40f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); // Background of checkbox, radio button, plot, slider, text input + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); + colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.67f, 0.40f, 0.40f, 0.60f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.67f, 0.40f, 0.40f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f); + colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 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_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Tips: if you need to change your scale multiple times, prefer calling this on a freshly initialized ImGuiStyle structure rather than scaling multiple times (because floating point multiplications are lossy). +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding *= scale_factor; + WindowMinSize *= scale_factor; + WindowRounding *= scale_factor; + ChildWindowRounding *= scale_factor; + FramePadding *= scale_factor; + FrameRounding *= scale_factor; + ItemSpacing *= scale_factor; + ItemInnerSpacing *= scale_factor; + TouchExtraPadding *= scale_factor; + IndentSpacing *= scale_factor; + ColumnsMinSpacing *= scale_factor; + ScrollbarSize *= scale_factor; + ScrollbarRounding *= scale_factor; + GrabMinSize *= scale_factor; + GrabRounding *= scale_factor; + DisplayWindowPadding *= scale_factor; + DisplaySafeAreaPadding *= scale_factor; } ImGuiIO::ImGuiIO() @@ -751,25 +810,21 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; LogFilename = "imgui_log.txt"; - Fonts = &GImDefaultFontAtlas; - FontGlobalScale = 1.0f; - FontDefault = NULL; - DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - MousePos = ImVec2(-1,-1); - MousePosPrev = ImVec2(-1,-1); MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; - 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 < ImGuiKey_COUNT; i++) KeyMap[i] = -1; KeyRepeatDelay = 0.250f; KeyRepeatRate = 0.050f; UserData = NULL; + Fonts = &GImDefaultFontAtlas; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + // User functions RenderDrawListsFn = NULL; MemAllocFn = malloc; @@ -780,6 +835,13 @@ ImGuiIO::ImGuiIO() ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; ImeWindowHandle = NULL; + // 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; + // Set OS X style defaults based on __APPLE__ compile time flag #ifdef __APPLE__ OSXBehaviors = true; @@ -936,15 +998,30 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char return NULL; } -// MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +static const char* ImAtoi(const char* src, int* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + int v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). // Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS int ImFormatString(char* buf, int buf_size, const char* fmt, ...) { - IM_ASSERT(buf_size > 0); va_list args; va_start(args, fmt); int w = vsnprintf(buf, buf_size, fmt, args); va_end(args); + if (buf == NULL) + return w; if (w == -1 || w >= buf_size) w = buf_size - 1; buf[w] = 0; @@ -953,13 +1030,15 @@ int ImFormatString(char* buf, int buf_size, const char* fmt, ...) int ImFormatStringV(char* buf, int buf_size, const char* fmt, va_list args) { - IM_ASSERT(buf_size > 0); int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; if (w == -1 || w >= buf_size) w = buf_size - 1; buf[w] = 0; return w; } +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Pass data_size==0 for zero-terminated strings // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. @@ -1240,12 +1319,12 @@ void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& float K = 0.f; if (g < b) { - const float tmp = g; g = b; b = tmp; + ImSwap(g, b); K = -1.f; } if (r < g) { - const float tmp = r; r = g; g = tmp; + ImSwap(r, g); K = -2.f / 6.f - K; } @@ -1595,7 +1674,7 @@ void ImGuiTextBuffer::appendv(const char* fmt, va_list args) va_list args_copy; va_copy(args_copy, args); - int len = vsnprintf(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. if (len <= 0) return; @@ -1608,7 +1687,7 @@ void ImGuiTextBuffer::appendv(const char* fmt, va_list args) } Buf.resize(needed_sz); - ImFormatStringV(&Buf[write_off] - 1, len+1, fmt, args_copy); + ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); } void ImGuiTextBuffer::append(const char* fmt, ...) @@ -1756,14 +1835,13 @@ ImGuiWindow::ImGuiWindow(const char* name) Name = ImStrdup(name); ID = ImHash(name, 0); IDStack.push_back(ID); - MoveId = GetID("#MOVE"); - Flags = 0; OrderWithinParent = 0; PosFloat = Pos = ImVec2(0.0f, 0.0f); Size = SizeFull = ImVec2(0.0f, 0.0f); SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); WindowPadding = ImVec2(0.0f, 0.0f); + MoveId = GetID("#MOVE"); Scroll = ImVec2(0.0f, 0.0f); ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); @@ -1774,6 +1852,7 @@ ImGuiWindow::ImGuiWindow(const char* name) Accessed = false; Collapsed = false; SkipItems = false; + Appearing = false; BeginCount = 0; PopupId = 0; AutoFitFramesX = AutoFitFramesY = -1; @@ -1782,7 +1861,7 @@ ImGuiWindow::ImGuiWindow(const char* name) AutoPosLastDirection = -1; HiddenFrames = 0; SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - SetWindowPosCenterWanted = false; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; ItemWidthDefault = 0.0f; @@ -1791,9 +1870,9 @@ ImGuiWindow::ImGuiWindow(const char* name) DrawList = (ImDrawList*)ImGui::MemAlloc(sizeof(ImDrawList)); IM_PLACEMENT_NEW(DrawList) ImDrawList(); DrawList->_OwnerName = Name; + ParentWindow = NULL; RootWindow = NULL; RootNonPopupWindow = NULL; - ParentWindow = NULL; FocusIdxAllCounter = FocusIdxTabCounter = -1; FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; @@ -1853,11 +1932,10 @@ ImGuiWindow* ImGui::GetParentWindow() void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) { ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); g.ActiveId = id; g.ActiveIdAllowOverlap = false; - g.ActiveIdIsJustActivated = true; - if (id) - g.ActiveIdIsAlive = true; + g.ActiveIdIsAlive |= (id != 0); g.ActiveIdWindow = window; } @@ -1880,27 +1958,51 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdIsAlive = true; } +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + // Advance cursor given item size for layout. void ImGui::ItemSize(const ImVec2& size, float text_offset_y) { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; // Always align ourselves on pixel boundaries - ImGuiContext& g = *GImGui; const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); - - //window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // Debug + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] window->DC.PrevLineHeight = line_height; window->DC.PrevLineTextBaseOffset = text_base_offset; window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); } void ImGui::ItemSize(const ImRect& bb, float text_offset_y) @@ -1911,73 +2013,96 @@ void ImGui::ItemSize(const ImRect& bb, float text_offset_y) // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). -bool ImGui::ItemAdd(const ImRect& bb, const ImGuiID* id) +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id) { - ImGuiWindow* window = GetCurrentWindow(); - window->DC.LastItemId = id ? *id : 0; - window->DC.LastItemRect = bb; - window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = false; - if (IsClippedEx(bb, id, false)) - return false; - - // This is a sensible default, but widgets are free to override it after calling ItemAdd() ImGuiContext& g = *GImGui; - if (IsMouseHoveringRect(bb.Min, bb.Max)) - { - // Matching the behavior of IsHovered() but allow if ActiveId==window->MoveID (we clicked on the window background) - // So that clicking on items with no active id such as Text() still returns true with IsItemHovered() - window->DC.LastItemHoveredRect = true; - if (g.HoveredRootWindow == window->RootWindow) - if (g.ActiveId == 0 || (id && g.ActiveId == *id) || g.ActiveIdAllowOverlap || (g.ActiveId == window->MoveId)) - if (IsWindowContentHoverable(window)) - window->DC.LastItemHoveredAndUsable = true; - } + ImGuiWindow* window = g.CurrentWindow; + const bool is_clipped = IsClippedEx(bb, id, false); + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemRectHoveredRect = false; + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + window->DC.LastItemRectHoveredRect = IsMouseHoveringRect(bb.Min, bb.Max); return true; } -bool ImGui::IsClippedEx(const ImRect& bb, const ImGuiID* id, bool clip_even_when_logged) +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered()) +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindowRead(); + + ImGuiWindow* window = g.CurrentWindow; + if (!window->DC.LastItemRectHoveredRect) + return false; + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + if (!IsWindowContentHoverable(window, flags)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (!bb.Overlaps(window->ClipRect)) - if (!id || *id != GImGui->ActiveId) + if (id == 0 || id != g.ActiveId) if (clip_even_when_logged || !g.LogEnabled) return true; return false; } -// NB: This is an internal helper. The user-facing IsItemHovered() is using data emitted from ItemAdd(), with a slightly different logic. -bool ImGui::IsHovered(const ImRect& bb, ImGuiID id, bool flatten_childs) -{ - ImGuiContext& g = *GImGui; - if (g.HoveredId == 0 || g.HoveredId == id || g.HoveredIdAllowOverlap) - { - ImGuiWindow* window = GetCurrentWindowRead(); - if (g.HoveredWindow == window || (flatten_childs && g.HoveredRootWindow == window->RootWindow)) - if ((g.ActiveId == 0 || g.ActiveId == id || g.ActiveIdAllowOverlap) && IsMouseHoveringRect(bb.Min, bb.Max)) - if (IsWindowContentHoverable(g.HoveredRootWindow)) - return true; - } - return false; -} - -bool ImGui::FocusableItemRegister(ImGuiWindow* window, bool is_active, bool tab_stop) +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) { ImGuiContext& g = *GImGui; - const bool allow_keyboard_focus = window->DC.AllowKeyboardFocus; + const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; window->FocusIdxAllCounter++; if (allow_keyboard_focus) window->FocusIdxTabCounter++; - // Process keyboard input at this point: TAB, Shift-TAB switch focus - // We can always TAB out of a widget that doesn't allow tabbing in. - if (tab_stop && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && is_active && IsKeyPressedMap(ImGuiKey_Tab)) - { - // Modulo on index will be applied at the end of frame once we've got the total counter of items. - window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); - } + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) return true; @@ -2125,16 +2250,9 @@ void ImGui::NewFrame() IM_ASSERT(g.Style.CurveTessellationTol > 0.0f); // Invalid style setting IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f); // Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations) + // Initialize on first frame if (!g.Initialized) - { - // Initialize on first frame - g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer)); - IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); - - IM_ASSERT(g.Settings.empty()); - LoadIniSettingsFromDisk(g.IO.IniFilename); - g.Initialized = true; - } + ImGui::Initialize(); SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); @@ -2151,14 +2269,29 @@ void ImGui::NewFrame() g.RenderDrawData.CmdLists = NULL; g.RenderDrawData.CmdListsCount = g.RenderDrawData.TotalVtxCount = g.RenderDrawData.TotalIdxCount = 0; + // Clear reference to active widget if the widget isn't alive anymore + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; // Update mouse input state - if (g.IO.MousePos.x < 0 && g.IO.MousePos.y < 0) - g.IO.MousePos = ImVec2(-9999.0f, -9999.0f); - if ((g.IO.MousePos.x < 0 && g.IO.MousePos.y < 0) || (g.IO.MousePosPrev.x < 0 && g.IO.MousePosPrev.y < 0)) // if mouse just appeared or disappeared (negative coordinate) we cancel out movement in MouseDelta - g.IO.MouseDelta = ImVec2(0.0f, 0.0f); - else + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); g.IO.MousePosPrev = g.IO.MousePos; for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { @@ -2187,9 +2320,6 @@ void ImGui::NewFrame() g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i])); } } - memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); - for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) - g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; // Calculate frame-rate for the user, as a purely luxurious feature g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; @@ -2197,43 +2327,30 @@ void ImGui::NewFrame() g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); - // Clear reference to active widget if the widget isn't alive anymore - g.HoveredIdPreviousFrame = g.HoveredId; - g.HoveredId = 0; - g.HoveredIdAllowOverlap = false; - if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) - ClearActiveID(); - g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdIsAlive = false; - g.ActiveIdIsJustActivated = false; - - // Handle user moving window (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows. - if (g.MovedWindowMoveId && g.MovedWindowMoveId == g.ActiveId) + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows. + if (g.MovingWindowMoveId && g.MovingWindowMoveId == g.ActiveId) { - KeepAliveID(g.MovedWindowMoveId); - IM_ASSERT(g.MovedWindow && g.MovedWindow->RootWindow); - IM_ASSERT(g.MovedWindow->RootWindow->MoveId == g.MovedWindowMoveId); + KeepAliveID(g.MovingWindowMoveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + IM_ASSERT(g.MovingWindow->MoveId == g.MovingWindowMoveId); if (g.IO.MouseDown[0]) { - if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoMove)) - { - g.MovedWindow->PosFloat += g.IO.MouseDelta; - if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoSavedSettings) && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)) - MarkIniSettingsDirty(g.MovedWindow); - } - FocusWindow(g.MovedWindow); + g.MovingWindow->RootWindow->PosFloat += g.IO.MouseDelta; + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + MarkIniSettingsDirty(g.MovingWindow->RootWindow); + FocusWindow(g.MovingWindow); } else { ClearActiveID(); - g.MovedWindow = NULL; - g.MovedWindowMoveId = 0; + g.MovingWindow = NULL; + g.MovingWindowMoveId = 0; } } else { - g.MovedWindow = NULL; - g.MovedWindowMoveId = 0; + g.MovingWindow = NULL; + g.MovingWindowMoveId = 0; } // Delay saving settings so we don't spam disk too much @@ -2244,12 +2361,12 @@ void ImGui::NewFrame() SaveIniSettingsToDisk(g.IO.IniFilename); } - // Find the window we are hovering. Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow - g.HoveredWindow = g.MovedWindow ? g.MovedWindow : FindHoveredWindow(g.IO.MousePos, false); - if (g.HoveredWindow && (g.HoveredWindow->Flags & ImGuiWindowFlags_ChildWindow)) - g.HoveredRootWindow = g.HoveredWindow->RootWindow; - else - g.HoveredRootWindow = g.MovedWindow ? g.MovedWindow->RootWindow : FindHoveredWindow(g.IO.MousePos, true); + // Find the window we are hovering + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow(g.IO.MousePos); + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; if (ImGuiWindow* modal_window = GetFrontMostModalRootWindow()) { @@ -2265,7 +2382,7 @@ void ImGui::NewFrame() g.ModalWindowDarkeningRatio = 0.0f; } - // Are we using inputs? Tell user so they can capture/discard the inputs away from the rest of their application. + // Update the WantCaptureMouse/WantCAptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application. // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership. int mouse_earliest_button_down = -1; bool mouse_any_down = false; @@ -2275,21 +2392,22 @@ void ImGui::NewFrame() g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); mouse_any_down |= g.IO.MouseDown[i]; if (g.IO.MouseDown[i]) - if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[mouse_earliest_button_down] > g.IO.MouseClickedTime[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) mouse_earliest_button_down = i; } bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; - if (g.CaptureMouseNextFrame != -1) - g.IO.WantCaptureMouse = (g.CaptureMouseNextFrame != 0); + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); else - g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.ActiveId != 0) || (!g.OpenPopupStack.empty()); - g.IO.WantCaptureKeyboard = (g.CaptureKeyboardNextFrame != -1) ? (g.CaptureKeyboardNextFrame != 0) : (g.ActiveId != 0); - g.IO.WantTextInput = (g.ActiveId != 0 && g.InputTextState.Id == g.ActiveId); + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != -1) ? (g.WantCaptureKeyboardNextFrame != 0) : (g.ActiveId != 0); + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0; g.MouseCursor = ImGuiMouseCursor_Arrow; - g.CaptureMouseNextFrame = g.CaptureKeyboardNextFrame = -1; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop between "application" and "imgui" we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) if (!mouse_avail_to_imgui) g.HoveredWindow = g.HoveredRootWindow = NULL; @@ -2319,7 +2437,6 @@ void ImGui::NewFrame() } // Pressing TAB activate widget focus - // NB: Don't discard FocusedWindow if it isn't active, so that a window that go on/off programatically won't lose its keyboard focus. if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false)) g.NavWindow->FocusIdxTabRequestNext = 0; @@ -2334,25 +2451,32 @@ void ImGui::NewFrame() // Closing the focused window restore focus to the first active root window in descending z-order if (g.NavWindow && !g.NavWindow->WasActive) - for (int i = g.Windows.Size-1; i >= 0; i--) - if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) - { - FocusWindow(g.Windows[i]); - break; - } + FocusPreviousWindow(); // No window should be open at the beginning of the frame. // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. g.CurrentWindowStack.resize(0); g.CurrentPopupStack.resize(0); - CloseInactivePopups(); + CloseInactivePopups(g.NavWindow); // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. ImGui::SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); - ImGui::Begin("Debug"); + ImGui::Begin("Debug##Default"); } -// NB: behavior of ImGui after Shutdown() is not tested/guaranteed at the moment. This function is merely here to free heap allocations. +void ImGui::Initialize() +{ + ImGuiContext& g = *GImGui; + g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer)); + IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); + + IM_ASSERT(g.Settings.empty()); + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.Initialized = true; +} + +// This function is merely here to free heap allocations. void ImGui::Shutdown() { ImGuiContext& g = *GImGui; @@ -2361,7 +2485,7 @@ void ImGui::Shutdown() if (g.IO.Fonts) // Testing for NULL to allow user to NULLify in case of running Shutdown() on multiple contexts. Bit hacky. g.IO.Fonts->Clear(); - // Cleanup of other data are conditional on actually having used ImGui. + // Cleanup of other data are conditional on actually having initialize ImGui. if (!g.Initialized) return; @@ -2380,7 +2504,7 @@ void ImGui::Shutdown() g.HoveredWindow = NULL; g.HoveredRootWindow = NULL; g.ActiveIdWindow = NULL; - g.MovedWindow = NULL; + g.MovingWindow = NULL; for (int i = 0; i < g.Settings.Size; i++) ImGui::MemFree(g.Settings[i].Name); g.Settings.clear(); @@ -2615,6 +2739,19 @@ static void AddWindowToRenderList(ImVector& out_render_list, ImGuiW } } +static void AddWindowToRenderListSelectLayer(ImGuiWindow* window) +{ + // FIXME: Generalize this with a proper layering system so e.g. user can draw in specific layers, below text, .. + ImGuiContext& g = *GImGui; + g.IO.MetricsActiveWindows++; + if (window->Flags & ImGuiWindowFlags_Popup) + AddWindowToRenderList(g.RenderDrawLists[1], window); + else if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToRenderList(g.RenderDrawLists[2], window); + else + AddWindowToRenderList(g.RenderDrawLists[0], window); +} + // When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) { @@ -2650,25 +2787,49 @@ void ImGui::EndFrame() g.CurrentWindow->Active = false; ImGui::End(); - // Click to focus window and start moving (after we're done with all our widgets) - if (g.ActiveId == 0 && g.HoveredId == 0 && g.IO.MouseClicked[0]) + if (g.ActiveId == 0 && g.HoveredId == 0) { - if (!(g.NavWindow && !g.NavWindow->WasActive && g.NavWindow->Active)) // Unless we just made a popup appear + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear { - if (g.HoveredRootWindow != NULL) + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) { - FocusWindow(g.HoveredWindow); - if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove)) + if (g.HoveredRootWindow != NULL) { - g.MovedWindow = g.HoveredWindow; - g.MovedWindowMoveId = g.HoveredRootWindow->MoveId; - SetActiveID(g.MovedWindowMoveId, g.HoveredRootWindow); + FocusWindow(g.HoveredWindow); + if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove)) + { + g.MovingWindow = g.HoveredWindow; + g.MovingWindowMoveId = g.MovingWindow->MoveId; + SetActiveID(g.MovingWindowMoveId, g.HoveredRootWindow); + } + } + else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); } } - else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->CloseInactivePopups to trigger) + if (g.IO.MouseClicked[1]) { - // Clicking on void disable focus - FocusWindow(NULL); + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + CloseInactivePopups(hovered_window_above_modal ? g.HoveredWindow : modal); } } } @@ -2716,16 +2877,7 @@ void ImGui::Render() { ImGuiWindow* window = g.Windows[i]; if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0) - { - // FIXME: Generalize this with a proper layering system so e.g. user can draw in specific layers, below text, .. - g.IO.MetricsActiveWindows++; - if (window->Flags & ImGuiWindowFlags_Popup) - AddWindowToRenderList(g.RenderDrawLists[1], window); - else if (window->Flags & ImGuiWindowFlags_Tooltip) - AddWindowToRenderList(g.RenderDrawLists[2], window); - else - AddWindowToRenderList(g.RenderDrawLists[0], window); - } + AddWindowToRenderListSelectLayer(window); } // Flatten layers @@ -2806,16 +2958,17 @@ void ImGui::LogText(const char* fmt, ...) // Internal version that takes a position to decide on newline placement and pad items according to their depth. // We split text into individual lines to add current tree level padding -static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* text_end) +static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = ImGui::GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; if (!text_end) text_end = ImGui::FindRenderedTextEnd(text, text_end); - const bool log_new_line = ref_pos.y > window->DC.LogLinePosY+1; - window->DC.LogLinePosY = ref_pos.y; + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; const char* text_remaining = text; if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth @@ -2860,7 +3013,7 @@ static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); + ImGuiWindow* window = g.CurrentWindow; // Hide anything after a '##' string const char* text_display_end; @@ -2880,14 +3033,14 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool { window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); if (g.LogEnabled) - LogRenderedText(pos, text, text_display_end); + LogRenderedText(&pos, text, text_display_end); } } void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); + ImGuiWindow* window = g.CurrentWindow; if (!text_end) text_end = text + strlen(text); // FIXME-OPT @@ -2897,7 +3050,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end { window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); if (g.LogEnabled) - LogRenderedText(pos, text, text_end); + LogRenderedText(&pos, text, text_end); } } @@ -2912,7 +3065,7 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons return; ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); + ImGuiWindow* window = g.CurrentWindow; // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; @@ -2939,14 +3092,14 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); } if (g.LogEnabled) - LogRenderedText(pos, text, text_display_end); + LogRenderedText(&pos, text, text_display_end); } // Render a rectangle shaped with optional rounding and borders void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) { - ImGuiWindow* window = GetCurrentWindow(); - + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); if (border && (window->Flags & ImGuiWindowFlags_ShowBorders)) { @@ -2957,7 +3110,8 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; if (window->Flags & ImGuiWindowFlags_ShowBorders) { window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding); @@ -2966,51 +3120,64 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) } // Render a triangle to denote expanded/collapsed state -void ImGui::RenderCollapseTriangle(ImVec2 p_min, bool is_open, float scale) +void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); + ImGuiWindow* window = g.CurrentWindow; const float h = g.FontSize * 1.00f; - const float r = h * 0.40f * scale; - ImVec2 center = p_min + ImVec2(h*0.50f, h*0.50f*scale); + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); ImVec2 a, b, c; - if (is_open) + switch (dir) { - center.y -= r*0.25f; - a = center + ImVec2(0,1)*r; - b = center + ImVec2(-0.866f,-0.5f)*r; - c = center + ImVec2(0.866f,-0.5f)*r; - } - else - { - a = center + ImVec2(1,0)*r; - b = center + ImVec2(-0.500f,0.866f)*r; - c = center + ImVec2(-0.500f,-0.866f)*r; + case ImGuiDir_Up: + r = -r; // ...fall through, no break! + case ImGuiDir_Down: + center.y -= r * 0.25f; + a = ImVec2(0,1) * r; + b = ImVec2(-0.866f,-0.5f) * r; + c = ImVec2(+0.866f,-0.5f) * r; + break; + case ImGuiDir_Left: + r = -r; // ...fall through, no break! + case ImGuiDir_Right: + a = ImVec2(1,0) * r; + b = ImVec2(-0.500f,+0.866f) * r; + c = ImVec2(-0.500f,-0.866f) * r; + break; + default: + IM_ASSERT(0); + break; } - window->DrawList->AddTriangleFilled(a, b, c, GetColorU32(ImGuiCol_Text)); + window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); } void ImGui::RenderBullet(ImVec2 pos) { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); } -void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col) +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - float start_x = (float)(int)(g.FontSize * 0.307f + 0.5f); - float rem_third = (float)(int)((g.FontSize - start_x) / 3.0f); - float bx = pos.x + 0.5f + start_x + rem_third; - float by = pos.y - 1.0f + (float)(int)(g.Font->Ascent * (g.FontSize / g.Font->FontSize) + 0.5f) + (float)(int)(g.Font->DisplayOffset.y); - window->DrawList->PathLineTo(ImVec2(bx - rem_third, by - rem_third)); + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); window->DrawList->PathLineTo(ImVec2(bx, by)); - window->DrawList->PathLineTo(ImVec2(bx + rem_third*2, by - rem_third*2)); - window->DrawList->PathStroke(col, false); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); } // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. @@ -3031,7 +3198,7 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex return ImVec2(0.0f, font_size); ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); - // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field) + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) const float font_scale = font_size / font->FontSize; const float character_spacing_x = 1.0f * font_scale; if (text_size.x > 0.0f) @@ -3047,7 +3214,7 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; if (g.LogEnabled) { // If logging is active, do not perform any clipping @@ -3072,7 +3239,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items // Find window given position, search front-to-back // FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected. -static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs) +static ImGuiWindow* FindHoveredWindow(ImVec2 pos) { ImGuiContext& g = *GImGui; for (int i = g.Windows.Size-1; i >= 0; i--) @@ -3082,10 +3249,8 @@ static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs) continue; if (window->Flags & ImGuiWindowFlags_NoInputs) continue; - if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) - continue; - // Using the clipped AABB so a child window will typically be clipped by its parent. + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); if (bb.Contains(pos)) return window; @@ -3099,7 +3264,7 @@ static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs) bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; // Clip ImRect rect_clipped(r_min, r_max); @@ -3120,7 +3285,7 @@ bool ImGui::IsAnyWindowHovered() static bool IsKeyPressedMap(ImGuiKey key, bool repeat) { const int key_index = GImGui->IO.KeyMap[key]; - return ImGui::IsKeyPressed(key_index, repeat); + return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false; } int ImGui::GetKeyIndex(ImGuiKey imgui_key) @@ -3137,6 +3302,25 @@ bool ImGui::IsKeyDown(int user_key_index) return GImGui->IO.KeysDown[user_key_index]; } +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + bool ImGui::IsKeyPressed(int user_key_index, bool repeat) { ImGuiContext& g = *GImGui; @@ -3145,13 +3329,8 @@ bool ImGui::IsKeyPressed(int user_key_index, bool repeat) const float t = g.IO.KeysDownDuration[user_key_index]; if (t == 0.0f) return true; - if (repeat && t > g.IO.KeyRepeatDelay) - { - float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; - if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) - return true; - } + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; return false; } @@ -3229,6 +3408,15 @@ ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() return g.IO.MousePos; } +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) { ImGuiContext& g = *GImGui; @@ -3261,24 +3449,12 @@ void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) void ImGui::CaptureKeyboardFromApp(bool capture) { - GImGui->CaptureKeyboardNextFrame = capture ? 1 : 0; + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; } void ImGui::CaptureMouseFromApp(bool capture) { - GImGui->CaptureMouseNextFrame = capture ? 1 : 0; -} - -bool ImGui::IsItemHovered() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemHoveredAndUsable; -} - -bool ImGui::IsItemRectHovered() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemHoveredRect; + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; } bool ImGui::IsItemActive() @@ -3286,7 +3462,7 @@ bool ImGui::IsItemActive() ImGuiContext& g = *GImGui; if (g.ActiveId) { - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; return g.ActiveId == window->DC.LastItemId; } return false; @@ -3294,7 +3470,7 @@ bool ImGui::IsItemActive() bool ImGui::IsItemClicked(int mouse_button) { - return IsMouseClicked(mouse_button) && IsItemHovered(); + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); } bool ImGui::IsAnyItemHovered() @@ -3358,7 +3534,7 @@ static ImRect GetVisibleRect() } // Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. -static void BeginTooltipEx(bool override_previous_tooltip) +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) { ImGuiContext& g = *GImGui; char window_name[16]; @@ -3371,12 +3547,13 @@ static void BeginTooltipEx(bool override_previous_tooltip) window->HiddenFrames = 1; ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", ++g.TooltipOverrideCount); } - ImGui::Begin(window_name, NULL, ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize); + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + ImGui::Begin(window_name, NULL, flags | extra_flags); } void ImGui::SetTooltipV(const char* fmt, va_list args) { - BeginTooltipEx(true); + BeginTooltipEx(0, true); TextV(fmt, args); EndTooltip(); } @@ -3391,7 +3568,7 @@ void ImGui::SetTooltip(const char* fmt, ...) void ImGui::BeginTooltip() { - BeginTooltipEx(false); + BeginTooltipEx(0, false); } void ImGui::EndTooltip() @@ -3416,6 +3593,11 @@ void ImGui::OpenPopupEx(ImGuiID id, bool reopen_existing) { g.OpenPopupStack.resize(current_stack_size+1); g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by CloseInactivePopups(). + // This is equivalent to what ClosePopupToLevel() does. + if (g.OpenPopupStack[current_stack_size].PopupId == id) + FocusWindow(window); } } @@ -3425,16 +3607,16 @@ void ImGui::OpenPopup(const char* str_id) OpenPopupEx(g.CurrentWindow->GetID(str_id), false); } -static void CloseInactivePopups() +static void CloseInactivePopups(ImGuiWindow* ref_window) { ImGuiContext& g = *GImGui; if (g.OpenPopupStack.empty()) return; // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. - // Don't close our own child popup windows + // Don't close our own child popup windows. int n = 0; - if (g.NavWindow) + if (ref_window) { for (n = 0; n < g.OpenPopupStack.Size; n++) { @@ -3445,24 +3627,25 @@ static void CloseInactivePopups() if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) continue; + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) bool has_focus = false; for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) - has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == g.NavWindow->RootWindow); + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); if (!has_focus) break; } } - if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a useful breakpoint on the line below - g.OpenPopupStack.resize(n); + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); } static ImGuiWindow* GetFrontMostModalRootWindow() { ImGuiContext& g = *GImGui; for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) - if (ImGuiWindow* front_most_popup = g.OpenPopupStack.Data[n].Window) - if (front_most_popup->Flags & ImGuiWindowFlags_Modal) - return front_most_popup; + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; return NULL; } @@ -3476,9 +3659,9 @@ static void ClosePopupToLevel(int remaining) g.OpenPopupStack.resize(remaining); } -static void ClosePopup(ImGuiID id) +void ImGui::ClosePopup(ImGuiID id) { - if (!ImGui::IsPopupOpen(id)) + if (!IsPopupOpen(id)) return; ImGuiContext& g = *GImGui; ClosePopupToLevel(g.OpenPopupStack.Size - 1); @@ -3504,18 +3687,18 @@ static inline void ClearSetNextWindowData() g.SetNextWindowSizeConstraint = g.SetNextWindowFocus = false; } -static bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (!ImGui::IsPopupOpen(id)) + if (!IsPopupOpen(id)) { ClearSetNextWindowData(); // We behave like Begin() and need to consume those values return false; } - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings; char name[20]; if (flags & ImGuiWindowFlags_ChildMenu) @@ -3523,11 +3706,11 @@ static bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) else ImFormatString(name, IM_ARRAYSIZE(name), "##popup_%08x", id); // Not recycling, so we can close/open during the same frame - bool is_open = ImGui::Begin(name, NULL, flags); + bool is_open = Begin(name, NULL, flags); if (!(window->Flags & ImGuiWindowFlags_ShowBorders)) g.CurrentWindow->Flags &= ~ImGuiWindowFlags_ShowBorders; if (!is_open) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) - ImGui::EndPopup(); + EndPopup(); return is_open; } @@ -3540,7 +3723,7 @@ bool ImGui::BeginPopup(const char* str_id) ClearSetNextWindowData(); // We behave like Begin() and need to consume those values return false; } - return BeginPopupEx(g.CurrentWindow->GetID(str_id), ImGuiWindowFlags_ShowBorders); + return BeginPopupEx(g.CurrentWindow->GetID(str_id), ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize); } bool ImGui::IsPopupOpen(ImGuiID id) @@ -3566,11 +3749,15 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags ext return false; } + // Center modal windows by default + if ((window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoSavedSettings; - bool is_open = ImGui::Begin(name, p_open, flags); + bool is_open = Begin(name, p_open, flags); if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) { - ImGui::EndPopup(); + EndPopup(); if (is_open) ClosePopup(id); return false; @@ -3584,49 +3771,64 @@ void ImGui::EndPopup() ImGuiWindow* window = GetCurrentWindow(); IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls IM_ASSERT(GImGui->CurrentPopupStack.Size > 0); - ImGui::End(); + End(); if (!(window->Flags & ImGuiWindowFlags_Modal)) - ImGui::PopStyleVar(); + PopStyleVar(); } -// This is a helper to handle the most simple case of associating one named popup to one given widget. -// 1. If you have many possible popups (for different "instances" of a same widget, or for wholly different widgets), you may be better off handling -// this yourself so you can store data relative to the widget that opened the popup instead of choosing different popup identifiers. -// 2. If you want right-clicking on the same item to reopen the popup at new location, use the same code replacing IsItemHovered() with IsItemRectHovered() -// and passing true to the OpenPopupEx(). -// Because: hovering an item in a window below the popup won't normally trigger is hovering behavior/coloring. The pattern of ignoring the fact that -// the item can be interacted with (because it is blocked by the active popup) may useful in some situation when e.g. large canvas as one item, content of menu -// driven by click position. +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id, true); + return true; + } + return false; +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) { - if (IsItemHovered() && IsMouseClicked(mouse_button)) - OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), false); - return BeginPopup(str_id); + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize); } bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) { if (!str_id) str_id = "window_context"; - if (IsMouseHoveringWindow() && IsMouseClicked(mouse_button)) - if (also_over_items || !IsAnyItemHovered()) - OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), true); - return BeginPopup(str_id); + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseClicked(mouse_button)) + if (IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize); } bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) { if (!str_id) str_id = "void_context"; - if (!IsMouseHoveringAnyWindow() && IsMouseClicked(mouse_button)) - OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), true); - return BeginPopup(str_id); + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (!IsAnyWindowHovered() && IsMouseClicked(mouse_button)) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize); } static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) { ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag const ImVec2 content_avail = ImGui::GetContentRegionAvail(); ImVec2 size = ImFloor(size_arg); @@ -3641,11 +3843,12 @@ static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, b char title[256]; if (name) - ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s.%08X", parent_window->Name, name, id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); else - ImFormatString(title, IM_ARRAYSIZE(title), "%s.%08X", parent_window->Name, id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); - bool ret = ImGui::Begin(title, NULL, size, -1.0f, flags); + ImGui::SetNextWindowSize(size); + bool ret = ImGui::Begin(title, NULL, flags); ImGuiWindow* child_window = ImGui::GetCurrentWindow(); child_window->AutoFitChildAxises = auto_fit_axises; if (!(parent_window->Flags & ImGuiWindowFlags_ShowBorders)) @@ -3682,13 +3885,12 @@ void ImGui::EndChild() sz.x = ImMax(4.0f, sz.x); if (window->AutoFitChildAxises & 0x02) sz.y = ImMax(4.0f, sz.y); - ImGui::End(); ImGuiWindow* parent_window = GetCurrentWindow(); ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); ItemSize(sz); - ItemAdd(bb, NULL); + ItemAdd(bb, 0); } } @@ -3697,17 +3899,17 @@ bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags ext { ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; - ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, style.Colors[ImGuiCol_FrameBg]); - ImGui::PushStyleVar(ImGuiStyleVar_ChildWindowRounding, style.FrameRounding); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); - return ImGui::BeginChild(id, size, (g.CurrentWindow->Flags & ImGuiWindowFlags_ShowBorders) ? true : false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); + PushStyleColor(ImGuiCol_ChildWindowBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildWindowRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + return BeginChild(id, size, (g.CurrentWindow->Flags & ImGuiWindowFlags_ShowBorders) ? true : false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); } void ImGui::EndChildFrame() { - ImGui::EndChild(); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); + EndChild(); + PopStyleVar(2); + PopStyleColor(); } // Save and compare stack sizes on Begin()/End() to detect usage errors @@ -3830,7 +4032,7 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl return window; } -static void ApplySizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) +static ImVec2 CalcSizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) { ImGuiContext& g = *GImGui; if (g.SetNextWindowSizeConstraint) @@ -3852,24 +4054,71 @@ static void ApplySizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) } if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) new_size = ImMax(new_size, g.Style.WindowMinSize); - window->SizeFull = new_size; + return new_size; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + ImVec2 size_auto_fit; + if ((flags & ImGuiWindowFlags_Tooltip) != 0) + { + // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. + size_auto_fit = window->SizeContents + window->WindowPadding - ImVec2(0.0f, style.ItemSpacing.y); + } + else + { + // Handling case of auto fit window not fitting on the screen (on either axis): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(window->SizeContents + window->WindowPadding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeFullWithConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < window->SizeContents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize * 2.0f; + size_auto_fit.y = ImMax(size_auto_fit.y - style.ItemSpacing.y, 0.0f); + } + return size_auto_fit; +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + float cr_x = window->ScrollTargetCenterRatio.x; + float cr_y = window->ScrollTargetCenterRatio.y; + if (window->ScrollTarget.x < FLT_MAX) + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + if (window->ScrollTarget.y < FLT_MAX) + scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x))); // == GetScrollMaxX for that window + scroll.y = ImMin(scroll.y, ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y))); // == GetScrollMaxY for that window + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & ImGuiWindowFlags_ComboBox) + return ImGuiCol_ComboBg; + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildWindowBg; + return ImGuiCol_WindowBg; } // Push a new ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. -// - 'size_on_first_use' for a regular window denote the initial size for first-time creation (no saved data) and isn't that useful. Use SetNextWindowSize() prior to calling Begin() for more flexible window manipulation. // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). // You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. // - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. // - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. -// - Passing non-zero 'size' is roughly equivalent to calling SetNextWindowSize(size, ImGuiCond_FirstUseEver) prior to calling Begin(). bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) -{ - return ImGui::Begin(name, p_open, ImVec2(0.f, 0.f), -1.0f, flags); -} - -bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha, ImGuiWindowFlags flags) { ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; @@ -3885,11 +4134,12 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us ImGuiWindow* window = FindWindowByName(name); if (!window) { + ImVec2 size_on_first_use = (g.SetNextWindowSizeCond != 0) ? g.SetNextWindowSizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. window = CreateNewWindow(name, size_on_first_use, flags); window_is_new = true; } - const int current_frame = ImGui::GetFrameCount(); + const int current_frame = g.FrameCount; const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); if (first_begin_of_the_frame) window->Flags = (ImGuiWindowFlags)flags; @@ -3903,41 +4153,45 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us CheckStacksSize(window, true); IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); - bool window_was_active = (window->LastFrameActive == current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; - window_was_active &= (window->PopupId == popup_ref.PopupId); - window_was_active &= (window == popup_ref.Window); + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); popup_ref.Window = window; g.CurrentPopupStack.push_back(popup_ref); window->PopupId = popup_ref.PopupId; } - const bool window_appearing_after_being_hidden = (window->HiddenFrames == 1); + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); // Process SetNextWindow***() calls bool window_pos_set_by_api = false, window_size_set_by_api = false; if (g.SetNextWindowPosCond) { - const ImVec2 backup_cursor_pos = window->DC.CursorPos; // FIXME: not sure of the exact reason of this saving/restore anymore :( need to look into that. - if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowPosAllowFlags |= ImGuiCond_Appearing; + if (window->Appearing) + window->SetWindowPosAllowFlags |= ImGuiCond_Appearing; window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) != 0; - if (window_pos_set_by_api && ImLengthSqr(g.SetNextWindowPosVal - ImVec2(-FLT_MAX,-FLT_MAX)) < 0.001f) + if (window_pos_set_by_api && ImLengthSqr(g.SetNextWindowPosPivot) > 0.00001f) { - window->SetWindowPosCenterWanted = true; // May be processed on the next frame if this is our first frame and we are measuring size + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.SetNextWindowPosVal; + window->SetWindowPosPivot = g.SetNextWindowPosPivot; window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); } else { SetWindowPos(window, g.SetNextWindowPosVal, g.SetNextWindowPosCond); } - window->DC.CursorPos = backup_cursor_pos; g.SetNextWindowPosCond = 0; } if (g.SetNextWindowSizeCond) { - if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowSizeAllowFlags |= ImGuiCond_Appearing; + if (window->Appearing) + window->SetWindowSizeAllowFlags |= ImGuiCond_Appearing; window_size_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0; SetWindowSize(window, g.SetNextWindowSizeVal, g.SetNextWindowSizeCond); g.SetNextWindowSizeCond = 0; @@ -3953,13 +4207,14 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us } if (g.SetNextWindowCollapsedCond) { - if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowCollapsedAllowFlags |= ImGuiCond_Appearing; + if (window->Appearing) + window->SetWindowCollapsedAllowFlags |= ImGuiCond_Appearing; SetWindowCollapsed(window, g.SetNextWindowCollapsedVal, g.SetNextWindowCollapsedCond); g.SetNextWindowCollapsedCond = 0; } if (g.SetNextWindowFocus) { - ImGui::SetWindowFocus(); + SetWindowFocus(); g.SetNextWindowFocus = false; } @@ -3969,11 +4224,11 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (!(g.CurrentWindowStack[root_idx]->Flags & ImGuiWindowFlags_ChildWindow)) break; for (root_non_popup_idx = root_idx; root_non_popup_idx > 0; root_non_popup_idx--) - if (!(g.CurrentWindowStack[root_non_popup_idx]->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + if (!(g.CurrentWindowStack[root_non_popup_idx]->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) || (g.CurrentWindowStack[root_non_popup_idx]->Flags & ImGuiWindowFlags_Modal)) break; window->ParentWindow = parent_window; window->RootWindow = g.CurrentWindowStack[root_idx]; - window->RootNonPopupWindow = g.CurrentWindowStack[root_non_popup_idx]; // This is merely for displaying the TitleBgActive color. + window->RootNonPopupWindow = g.CurrentWindowStack[root_non_popup_idx]; // Used to display TitleBgActive color and for selecting which window to use for NavWindowing // When reusing window again multiple times a frame, just append content (don't need to setup again) if (first_begin_of_the_frame) @@ -3994,7 +4249,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us else PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true); - if (!window_was_active) + if (window_just_activated_by_user) { // Popup first latch mouse position, will position itself when it appears next frame window->AutoPosLastDirection = -1; @@ -4028,7 +4283,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us // Hide popup/tooltip window when first appearing while we measure size (because we recycle them) if (window->HiddenFrames > 0) window->HiddenFrames--; - if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && !window_was_active) + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) { window->HiddenFrames = 1; if (flags & ImGuiWindowFlags_AlwaysAutoResize) @@ -4042,26 +4297,8 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us // Lock window padding so that altering the ShowBorders flag for children doesn't have side-effects. window->WindowPadding = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup))) ? ImVec2(0,0) : style.WindowPadding; - // Calculate auto-fit size - ImVec2 size_auto_fit; - if ((flags & ImGuiWindowFlags_Tooltip) != 0) - { - // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. - size_auto_fit = window->SizeContents + window->WindowPadding - ImVec2(0.0f, style.ItemSpacing.y); - } - else - { - size_auto_fit = ImClamp(window->SizeContents + window->WindowPadding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); - - // Handling case of auto fit window not fitting in screen on one axis, we are growing auto fit size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. - if (size_auto_fit.x < window->SizeContents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) - size_auto_fit.y += style.ScrollbarSize; - if (size_auto_fit.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) - size_auto_fit.x += style.ScrollbarSize; - size_auto_fit.y = ImMax(size_auto_fit.y - style.ItemSpacing.y, 0.0f); - } - - // Handle automatic resize + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window); if (window->Collapsed) { // We still process initial auto-fit on collapsed windows to get a window width, @@ -4071,13 +4308,13 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (window->AutoFitFramesY > 0) window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; } - else + else if (!window_size_set_by_api) { - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window_size_set_by_api) + if (flags & ImGuiWindowFlags_AlwaysAutoResize) { window->SizeFull = size_auto_fit; } - else if ((window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) && !window_size_set_by_api) + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) { // Auto-fit only grows during the first few frames if (window->AutoFitFramesX > 0) @@ -4089,7 +4326,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us } // Apply minimum/maximum window size constraints and final size - ApplySizeFullWithConstraint(window, window->SizeFull); + window->SizeFull = CalcSizeFullWithConstraint(window, window->SizeFull); window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; // POSITION @@ -4102,17 +4339,16 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us } if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) { + IM_ASSERT(window_size_set_by_api); // Submitted by BeginChild() window->Pos = window->PosFloat = parent_window->DC.CursorPos; - window->Size = window->SizeFull = size_on_first_use; // NB: argument name 'size_on_first_use' misleading here, it's really just 'size' as provided by user passed via BeginChild()->Begin(). + window->Size = window->SizeFull; } - bool window_pos_center = false; - window_pos_center |= (window->SetWindowPosCenterWanted && window->HiddenFrames == 0); - window_pos_center |= ((flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api && window_appearing_after_being_hidden); - if (window_pos_center) + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0); + if (window_pos_with_pivot) { - // Center (any sort of window) - SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, fullscreen_rect.GetCenter() - window->SizeFull * 0.5f), 0); + // Position given a pivot (e.g. for centering) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); } else if (flags & ImGuiWindowFlags_ChildMenu) { @@ -4127,7 +4363,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us rect_to_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); } - else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_appearing_after_being_hidden) + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) { ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); @@ -4136,10 +4372,11 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us // Position tooltip (always follows mouse) if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) { - ImRect rect_to_avoid(g.IO.MousePos.x - 16, g.IO.MousePos.y - 8, g.IO.MousePos.x + 24, g.IO.MousePos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead? - window->PosFloat = FindBestPopupWindowPos(g.IO.MousePos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + ImVec2 ref_pos = g.IO.MousePos; + ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead? + window->PosFloat = FindBestPopupWindowPos(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); if (window->AutoPosLastDirection == -1) - window->PosFloat = g.IO.MousePos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. } // Clamp position so it stays visible @@ -4167,23 +4404,8 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; // Apply scrolling - if (window->ScrollTarget.x < FLT_MAX) - { - window->Scroll.x = window->ScrollTarget.x; - window->ScrollTarget.x = FLT_MAX; - } - if (window->ScrollTarget.y < FLT_MAX) - { - float center_ratio = window->ScrollTargetCenterRatio.y; - window->Scroll.y = window->ScrollTarget.y - ((1.0f - center_ratio) * (window->TitleBarHeight() + window->MenuBarHeight())) - (center_ratio * (window->SizeFull.y - window->ScrollbarSizes.y)); - window->ScrollTarget.y = FLT_MAX; - } - window->Scroll = ImMax(window->Scroll, ImVec2(0.0f, 0.0f)); - if (!window->Collapsed && !window->SkipItems) - { - window->Scroll.x = ImMin(window->Scroll.x, GetScrollMaxX()); - window->Scroll.y = ImMin(window->Scroll.y, GetScrollMaxY()); - } + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); // Modal window darkens what is behind them if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) @@ -4194,8 +4416,8 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us const float window_rounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding; if (window->Collapsed) { - // Draw title bar only - RenderFrame(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding); + // Title bar only + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding); } else { @@ -4204,30 +4426,34 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) { // Manual resize + // Using the FlattenChilds button flag, we make the resize button accessible even if we are hovering over a child window const ImVec2 br = window->Rect().GetBR(); const ImRect resize_rect(br - ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f), br); const ImGuiID resize_id = window->GetID("#RESIZE"); bool hovered, held; ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds); resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); - if (hovered || held) g.MouseCursor = ImGuiMouseCursor_ResizeNWSE; + ImVec2 size_target(FLT_MAX,FLT_MAX); if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) { // Manual auto-fit when double-clicking - ApplySizeFullWithConstraint(window, size_auto_fit); - MarkIniSettingsDirty(window); + size_target = size_auto_fit; ClearActiveID(); } else if (held) { // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position - ApplySizeFullWithConstraint(window, (g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos); - MarkIniSettingsDirty(window); + size_target = (g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos; } + if (size_target.x != FLT_MAX && size_target.y != FLT_MAX) + { + window->SizeFull = CalcSizeFullWithConstraint(window, size_target); + MarkIniSettingsDirty(window); + } window->Size = window->SizeFull; title_bar_rect = window->TitleBarRect(); } @@ -4241,23 +4467,13 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us window->BorderSize = (flags & ImGuiWindowFlags_ShowBorders) ? 1.0f : 0.0f; // Window background, Default Alpha - ImGuiCol bg_color_idx = ImGuiCol_WindowBg; - if ((flags & ImGuiWindowFlags_ComboBox) != 0) - bg_color_idx = ImGuiCol_ComboBg; - else if ((flags & ImGuiWindowFlags_Tooltip) != 0 || (flags & ImGuiWindowFlags_Popup) != 0) - bg_color_idx = ImGuiCol_PopupBg; - else if ((flags & ImGuiWindowFlags_ChildWindow) != 0) - bg_color_idx = ImGuiCol_ChildWindowBg; - ImVec4 bg_color = style.Colors[bg_color_idx]; // We don't use GetColorU32() because bg_alpha is assigned (not multiplied) below - if (bg_alpha >= 0.0f) - bg_color.w = bg_alpha; - bg_color.w *= style.Alpha; - if (bg_color.w > 0.0f) - window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, ColorConvertFloat4ToU32(bg_color), window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImGuiCorner_All : ImGuiCorner_BottomLeft|ImGuiCorner_BottomRight); + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImGuiCorner_All : ImGuiCorner_BotLeft|ImGuiCorner_BotRight); // Title bar + const bool is_focused = g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow; if (!(flags & ImGuiWindowFlags_NoTitleBar)) - window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32((g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImGuiCorner_TopLeft|ImGuiCorner_TopRight); + window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImGuiCorner_TopLeft|ImGuiCorner_TopRight); // Menu bar if (flags & ImGuiWindowFlags_MenuBar) @@ -4270,9 +4486,9 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us // Scrollbars if (window->ScrollbarX) - Scrollbar(window, true); + Scrollbar(ImGuiLayoutType_Horizontal); if (window->ScrollbarY) - Scrollbar(window, false); + Scrollbar(ImGuiLayoutType_Vertical); // Render resize grip // (after the input handling so we don't have a frame of latency) @@ -4316,13 +4532,11 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; window->DC.ChildWindows.resize(0); window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ItemFlags = ImGuiItemFlags_Default_; window->DC.ItemWidth = window->ItemWidthDefault; window->DC.TextWrapPos = -1.0f; // disabled - window->DC.AllowKeyboardFocus = true; - window->DC.ButtonRepeat = false; + window->DC.ItemFlagsStack.resize(0); window->DC.ItemWidthStack.resize(0); - window->DC.AllowKeyboardFocusStack.resize(0); - window->DC.ButtonRepeatStack.resize(0); window->DC.TextWrapPosStack.resize(0); window->DC.ColumnsCurrent = 0; window->DC.ColumnsCount = 1; @@ -4332,7 +4546,13 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us window->DC.TreeDepth = 0; window->DC.StateStorage = &window->StateStorage; window->DC.GroupStack.resize(0); - window->MenuColumns.Update(3, style.ItemSpacing.x, !window_was_active); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } if (window->AutoFitFramesX > 0) window->AutoFitFramesX--; @@ -4340,25 +4560,30 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us window->AutoFitFramesY--; // New windows appears in front (we need to do that AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) - if (!window_was_active && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) if (!(flags & (ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) FocusWindow(window); // Title bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) { + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + { + RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + } + + // Close button if (p_open != NULL) { - const float pad = 2.0f; - const float rad = (window->TitleBarHeight() - pad*2.0f) * 0.5f; - if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad)) + const float PAD = 2.0f; + const float rad = (window->TitleBarHeight() - PAD*2.0f) * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad)) *p_open = false; } + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) const ImVec2 text_size = CalcTextSize(name, NULL, true); - if (!(flags & ImGuiWindowFlags_NoCollapse)) - RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f); - ImVec2 text_min = window->Pos; ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y*2 + text_size.y); ImRect clip_rect; @@ -4384,24 +4609,31 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) ImGui::LogToClipboard(); */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. + window->InnerRect.Min.x = title_bar_rect.Min.x; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight(); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); } // Inner clipping rectangle - // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame - // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. - const ImRect title_bar_rect = window->TitleBarRect(); + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. const float border_size = window->BorderSize; - // Force round to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. ImRect clip_rect; - clip_rect.Min.x = ImFloor(0.5f + title_bar_rect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); - clip_rect.Min.y = ImFloor(0.5f + title_bar_rect.Max.y + window->MenuBarHeight() + border_size); - clip_rect.Max.x = ImFloor(0.5f + window->Pos.x + window->Size.x - window->ScrollbarSizes.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); - clip_rect.Max.y = ImFloor(0.5f + window->Pos.y + window->Size.y - window->ScrollbarSizes.y - border_size); + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + border_size); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - border_size); PushClipRect(clip_rect.Min, clip_rect.Max, true); - // Clear 'accessed' flag last thing + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) if (first_begin_of_the_frame) window->Accessed = false; + window->BeginCount++; g.SetNextWindowSizeConstraint = false; @@ -4428,6 +4660,32 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us return !window->SkipItems; } +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()+Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter, however this was very misleading because in most cases it would only affect the window when it didn't have storage in the .ini file. + if (size_on_first_use.x != 0.0f || size_on_first_use.y != 0.0f) + SetNextWindowSize(size_on_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: we could override the window background alpha with a parameter. This is actually tricky to reproduce manually because: + // (1) there are multiple variants of WindowBg (popup, tooltip, etc.) and (2) you can't call PushStyleColor before Begin and PopStyleColor just after Begin() because of how CheckStackSizes() behave. + // The user-side solution is to do backup = GetStyleColorVec4(ImGuiCol_xxxBG), PushStyleColor(ImGuiCol_xxxBg), Begin, PushStyleColor(ImGuiCol_xxxBg, backup), [...], PopStyleColor(), End(); PopStyleColor() - which is super awkward. + // The alpha override was rarely used but for now we'll leave the Begin() variant around for a bit. We may either lift the constraint on CheckStackSizes() either add a SetNextWindowBgAlpha() helper that does it magically. + ImGuiContext& g = *GImGui; + const ImGuiCol bg_color_idx = GetWindowBgColorIdxFromFlags(flags); + const ImVec4 bg_color_backup = g.Style.Colors[bg_color_idx]; + if (bg_alpha_override >= 0.0f) + g.Style.Colors[bg_color_idx].w = bg_alpha_override; + + bool ret = Begin(name, p_open, flags); + + if (bg_alpha_override >= 0.0f) + g.Style.Colors[bg_color_idx] = bg_color_backup; + return ret; +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + void ImGui::End() { ImGuiContext& g = *GImGui; @@ -4455,9 +4713,12 @@ void ImGui::End() // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. -static void Scrollbar(ImGuiWindow* window, bool horizontal) +void ImGui::Scrollbar(ImGuiLayoutType direction) { ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); @@ -4477,10 +4738,10 @@ static void Scrollbar(ImGuiWindow* window, bool horizontal) float window_rounding = (window->Flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding; int window_rounding_corners; if (horizontal) - window_rounding_corners = ImGuiCorner_BottomLeft | (other_scrollbar ? 0 : ImGuiCorner_BottomRight); + window_rounding_corners = ImGuiCorner_BotLeft | (other_scrollbar ? 0 : ImGuiCorner_BotRight); else - window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImGuiCorner_TopRight : 0) | (other_scrollbar ? 0 : ImGuiCorner_BottomRight); - window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_ScrollbarBg), window_rounding, window_rounding_corners); + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImGuiCorner_TopRight : 0) | (other_scrollbar ? 0 : ImGuiCorner_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window_rounding, window_rounding_corners); bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) @@ -4500,7 +4761,7 @@ static void Scrollbar(ImGuiWindow* window, bool horizontal) bool held = false; bool hovered = false; const bool previously_held = (g.ActiveId == id); - ImGui::ButtonBehavior(bb, id, &hovered, &held); + ButtonBehavior(bb, id, &hovered, &held); float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); float scroll_ratio = ImSaturate(scroll_v / scroll_max); @@ -4513,7 +4774,7 @@ static void Scrollbar(ImGuiWindow* window, bool horizontal) // Click position in scrollbar normalized space (0.0f->1.0f) const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); - ImGui::SetHoveredID(id); + SetHoveredID(id); bool seek_absolute = false; if (!previously_held) @@ -4549,7 +4810,7 @@ static void Scrollbar(ImGuiWindow* window, bool horizontal) } // Render - const ImU32 grab_col = ImGui::GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); if (horizontal) window->DrawList->AddRectFilled(ImVec2(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y), ImVec2(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y), grab_col, style.ScrollbarRounding); else @@ -4589,6 +4850,17 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.Windows.push_back(window); } +void ImGui::FocusPreviousWindow() +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + { + FocusWindow(g.Windows[i]); + return; + } +} + void ImGui::PushItemWidth(float item_width) { ImGuiWindow* window = GetCurrentWindow(); @@ -4596,12 +4868,12 @@ void ImGui::PushItemWidth(float item_width) window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); } -static void PushMultiItemsWidths(int components, float w_full) +void ImGui::PushMultiItemsWidths(int components, float w_full) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImGuiWindow* window = GetCurrentWindow(); const ImGuiStyle& style = GImGui->Style; if (w_full <= 0.0f) - w_full = ImGui::CalcItemWidth(); + w_full = CalcItemWidth(); const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); window->DC.ItemWidthStack.push_back(w_item_last); @@ -4666,32 +4938,41 @@ void ImGui::PopFont() SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); } -void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); - window->DC.AllowKeyboardFocus = allow_keyboard_focus; - window->DC.AllowKeyboardFocusStack.push_back(allow_keyboard_focus); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); } void ImGui::PopAllowKeyboardFocus() { - ImGuiWindow* window = GetCurrentWindow(); - window->DC.AllowKeyboardFocusStack.pop_back(); - window->DC.AllowKeyboardFocus = window->DC.AllowKeyboardFocusStack.empty() ? true : window->DC.AllowKeyboardFocusStack.back(); + PopItemFlag(); } void ImGui::PushButtonRepeat(bool repeat) { - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ButtonRepeat = repeat; - window->DC.ButtonRepeatStack.push_back(repeat); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } void ImGui::PopButtonRepeat() { - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ButtonRepeatStack.pop_back(); - window->DC.ButtonRepeat = window->DC.ButtonRepeatStack.empty() ? false : window->DC.ButtonRepeatStack.back(); + PopItemFlag(); } void ImGui::PushTextWrapPos(float wrap_pos_x) @@ -4745,7 +5026,7 @@ struct ImGuiStyleVarInfo { ImGuiDataType Type; ImU32 Offset; - void* GetVarPtr() const { return (void*)((unsigned char*)&GImGui->Style + Offset); } + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } }; static const ImGuiStyleVarInfo GStyleVarInfo[ImGuiStyleVar_Count_] = @@ -4775,8 +5056,9 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); if (var_info->Type == ImGuiDataType_Float) { - float* pvar = (float*)var_info->GetVarPtr(); - GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; return; } @@ -4788,8 +5070,9 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); if (var_info->Type == ImGuiDataType_Float2) { - ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(); - GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; return; } @@ -4803,9 +5086,9 @@ void ImGui::PopStyleVar(int count) { ImGuiStyleMod& backup = g.StyleModifiers.back(); const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); - if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr()) = backup.BackupFloat[0]; - else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr()) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); - else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr()) = backup.BackupInt[0]; + if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0]; + else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); + else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0]; g.StyleModifiers.pop_back(); count--; } @@ -4827,8 +5110,8 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; case ImGuiCol_FrameBgActive: return "FrameBgActive"; case ImGuiCol_TitleBg: return "TitleBg"; - case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; case ImGuiCol_MenuBarBg: return "MenuBarBg"; case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; @@ -4864,16 +5147,18 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) return "Unknown"; } -bool ImGui::IsWindowHovered() +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function ImGuiContext& g = *GImGui; - return g.HoveredWindow == g.CurrentWindow && IsWindowContentHoverable(g.HoveredRootWindow); -} - -bool ImGui::IsWindowRectHovered() -{ - ImGuiContext& g = *GImGui; - return g.HoveredWindow == g.CurrentWindow; + if (g.HoveredWindow != g.CurrentWindow) + return false; + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveIdWindow != g.CurrentWindow) + return false; + return true; } bool ImGui::IsWindowFocused() @@ -4894,10 +5179,18 @@ bool ImGui::IsRootWindowOrAnyChildFocused() return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; } -bool ImGui::IsRootWindowOrAnyChildHovered() +bool ImGui::IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags) { + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function ImGuiContext& g = *GImGui; - return g.HoveredRootWindow && (g.HoveredRootWindow == g.CurrentWindow->RootWindow) && IsWindowContentHoverable(g.HoveredRootWindow); + if (!g.HoveredRootWindow || (g.HoveredRootWindow != g.CurrentWindow->RootWindow)) + return false; + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveIdWindow != g.CurrentWindow) + return false; + return true; } float ImGui::GetWindowWidth() @@ -4932,7 +5225,7 @@ static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) if (cond && (window->SetWindowPosAllowFlags & cond) == 0) return; window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - window->SetWindowPosCenterWanted = false; + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); // Set const ImVec2 old_pos = window->Pos; @@ -4997,8 +5290,7 @@ void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) { - ImGuiWindow* window = FindWindowByName(name); - if (window) + if (ImGuiWindow* window = FindWindowByName(name)) SetWindowSize(window, size, cond); } @@ -5020,7 +5312,14 @@ void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) bool ImGui::IsWindowCollapsed() { - return GImGui->CurrentWindow->Collapsed; + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; } void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) @@ -5048,17 +5347,11 @@ void ImGui::SetWindowFocus(const char* name) } } -void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond) +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) { ImGuiContext& g = *GImGui; g.SetNextWindowPosVal = pos; - g.SetNextWindowPosCond = cond ? cond : ImGuiCond_Always; -} - -void ImGui::SetNextWindowPosCenter(ImGuiCond cond) -{ - ImGuiContext& g = *GImGui; - g.SetNextWindowPosVal = ImVec2(-FLT_MAX, -FLT_MAX); + g.SetNextWindowPosPivot = pivot; g.SetNextWindowPosCond = cond ? cond : ImGuiCond_Always; } @@ -5310,6 +5603,7 @@ void ImGui::SetScrollHere(float center_y_ratio) void ImGui::SetKeyboardFocusHere(int offset) { + IM_ASSERT(offset >= -1); // -1 is allowed but not below ImGuiWindow* window = GetCurrentWindow(); window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; window->FocusIdxTabRequestNext = INT_MAX; @@ -5404,6 +5698,7 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) if (text_end == NULL) text_end = text + strlen(text); // FIXME-OPT + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); const float wrap_pos_x = window->DC.TextWrapPos; const bool wrap_enabled = wrap_pos_x >= 0.0f; if (text_end - text > 2000 && !wrap_enabled) @@ -5414,7 +5709,6 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. const char* line = text; const float line_height = GetTextLineHeight(); - const ImVec2 text_pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrentLineTextBaseOffset); const ImRect clip_rect = window->ClipRect; ImVec2 text_size(0,0); @@ -5448,7 +5742,7 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) while (line < text_end) { const char* line_end = strchr(line, '\n'); - if (IsClippedEx(line_rect, NULL, false)) + if (IsClippedEx(line_rect, 0, false)) break; const ImVec2 line_size = CalcTextSize(line, line_end, false); @@ -5480,7 +5774,7 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) ImRect bb(text_pos, text_pos + text_size); ItemSize(bb); - ItemAdd(bb, NULL); + ItemAdd(bb, 0); } else { @@ -5488,10 +5782,9 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); // Account of baseline offset - ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); ImRect bb(text_pos, text_pos + text_size); ItemSize(text_size); - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) return; // Render (we don't hide text after ## in this end-user function) @@ -5499,16 +5792,15 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) } } -void ImGui::AlignFirstTextHeightToWidgets() +void ImGui::AlignTextToFramePadding() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; - // Declare a dummy item size to that upcoming items that are smaller will center-align on the newly expanded line height. ImGuiContext& g = *GImGui; - ItemSize(ImVec2(0, g.FontSize + g.Style.FramePadding.y*2), g.Style.FramePadding.y); - SameLine(0, 0); + window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); } // Add a label+text combo aligned to other label+value widgets @@ -5526,7 +5818,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, NULL)) + if (!ItemAdd(total_bb, 0)) return; // Render @@ -5545,19 +5837,6 @@ void ImGui::LabelText(const char* label, const char* fmt, ...) va_end(args); } -static inline bool IsWindowContentHoverable(ImGuiWindow* window) -{ - // An active popup disable hovering on other windows (apart from its own children) - // FIXME-OPT: This could be cached/stored within the window. - ImGuiContext& g = *GImGui; - if (g.NavWindow) - if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) - if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) != 0 && focused_root_window->WasActive && focused_root_window != window->RootWindow) - return false; - - return true; -} - bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; @@ -5575,11 +5854,18 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) flags |= ImGuiButtonFlags_PressedOnClickRelease; + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChilds) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + bool pressed = false; - bool hovered = IsHovered(bb, id, (flags & ImGuiButtonFlags_FlattenChilds) != 0); + bool hovered = ItemHoverable(bb, id); + + if ((flags & ImGuiButtonFlags_FlattenChilds) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + if (hovered) { - SetHoveredID(id); if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat @@ -5596,8 +5882,12 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) { pressed = true; - ClearActiveID(); + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID FocusWindow(window); + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; } if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) { @@ -5657,10 +5947,10 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags const ImRect bb(pos, pos + size); ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(bb, &id)) + if (!ItemAdd(bb, id)) return false; - if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); @@ -5704,7 +5994,7 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(bb); - if (!ItemAdd(bb, &id)) + if (!ItemAdd(bb, id)) return false; bool hovered, held; @@ -5748,7 +6038,7 @@ void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& if (border_col.w > 0.0f) bb.Max += ImVec2(2,2); ItemSize(bb); - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) return; if (border_col.w > 0.0f) @@ -5785,7 +6075,7 @@ bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const I const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); ItemSize(bb); - if (!ItemAdd(bb, &id)) + if (!ItemAdd(bb, id)) return false; bool hovered, held; @@ -5807,7 +6097,7 @@ void ImGui::LogToTTY(int max_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; g.LogEnabled = true; g.LogFile = stdout; @@ -5822,7 +6112,7 @@ void ImGui::LogToFile(int max_depth, const char* filename) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; if (!filename) { @@ -5849,7 +6139,7 @@ void ImGui::LogToClipboard(int max_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; g.LogEnabled = true; g.LogFile = NULL; @@ -5962,14 +6252,14 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; - const ImVec2 padding = display_frame ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); if (!label_end) label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); // We vertically grow up to current line height up the typical widget height. - const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset - padding.y); // Latch before ItemSize changes it + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); ImRect bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); if (display_frame) @@ -5984,10 +6274,10 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing - // (Ideally we'd want to add a flag for the user to specify we want want the hit test to be done up to the right side of the content or not) + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) const ImRect interact_bb = display_frame ? bb : ImRect(bb.Min.x, bb.Min.y, bb.Min.x + text_width + style.ItemSpacing.x*2, bb.Max.y); bool is_open = TreeNodeBehaviorIsOpen(id, flags); - if (!ItemAdd(interact_bb, &id)) + if (!ItemAdd(interact_bb, id)) { if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushRawID(id); @@ -6021,20 +6311,20 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - const ImVec2 text_pos = bb.Min + ImVec2(text_offset_x, padding.y + text_base_offset_y); + const ImVec2 text_pos = bb.Min + ImVec2(text_offset_x, text_base_offset_y); if (display_frame) { // Framed type RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); - RenderCollapseTriangle(bb.Min + padding + ImVec2(0.0f, text_base_offset_y), is_open, 1.0f); + RenderTriangle(bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); if (g.LogEnabled) { // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. const char log_prefix[] = "\n##"; const char log_suffix[] = "##"; - LogRenderedText(text_pos, log_prefix, log_prefix+3); + LogRenderedText(&text_pos, log_prefix, log_prefix+3); RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size); - LogRenderedText(text_pos, log_suffix+1, log_suffix+3); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); } else { @@ -6050,9 +6340,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); else if (!(flags & ImGuiTreeNodeFlags_Leaf)) - RenderCollapseTriangle(bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open, 0.70f); + RenderTriangle(bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); if (g.LogEnabled) - LogRenderedText(text_pos, ">"); + LogRenderedText(&text_pos, ">"); RenderText(text_pos, label, label_end, false); } @@ -6088,8 +6378,11 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. ImGuiContext& g = *GImGui; float button_sz = g.FontSize * 0.5f; + ImGuiItemHoveredDataBackup last_item_backup; + last_item_backup.Backup(); if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz)) *p_open = false; + last_item_backup.Restore(); } return is_open; @@ -6196,6 +6489,8 @@ float ImGui::GetTreeNodeToLabelSpacing() void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) { ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; g.SetNextTreeNodeOpenVal = is_open; g.SetNextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; } @@ -6257,7 +6552,7 @@ void ImGui::Bullet() const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); ItemSize(bb); - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) { SameLine(0, style.FramePadding.x*2); return; @@ -6285,7 +6580,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding ItemSize(bb); - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) return; // Render @@ -6416,6 +6711,7 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label ImGuiWindow* window = GetCurrentWindow(); // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id SetActiveID(g.ScalarAsInputTextId, window); SetHoveredID(0); FocusableItemUnregister(window); @@ -6429,11 +6725,6 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label g.ScalarAsInputTextId = g.ActiveId; SetHoveredID(id); } - else if (g.ActiveId != g.ScalarAsInputTextId) - { - // Release - g.ScalarAsInputTextId = 0; - } if (text_value_changed) return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); return false; @@ -6451,10 +6742,12 @@ int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) fmt++; if (*fmt == '.') { - precision = atoi(fmt + 1); + fmt = ImAtoi(fmt + 1, &precision); if (precision < 0 || precision > 10) precision = default_precision; } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; break; } return precision; @@ -6471,6 +6764,8 @@ float ImGui::RoundScalar(float value, int decimal_precision) // Round past decimal precision // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 // FIXME: Investigate better rounding methods + if (decimal_precision < 0) + return value; const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision); bool negative = value < 0.0f; value = fabsf(value); @@ -6522,7 +6817,7 @@ bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v const float grab_padding = 2.0f; const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); float grab_sz; - if (decimal_precision > 0) + if (decimal_precision != 0) grab_sz = ImMin(style.GrabMinSize, slider_sz); else grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit @@ -6641,15 +6936,12 @@ bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, c const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); // NB- we don't call ItemSize() yet because we may turn into a text edit box below - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) { ItemSize(total_bb, style.FramePadding.y); return false; } - - const bool hovered = IsHovered(frame_bb, id); - if (hovered) - SetHoveredID(id); + const bool hovered = ItemHoverable(frame_bb, id); if (!display_format) display_format = "%.3f"; @@ -6657,12 +6949,11 @@ bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, c // Tabbing or CTRL-clicking on Slider turns it into an input box bool start_text_input = false; - const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id); + const bool tab_focus_requested = FocusableItemRegister(window, id); if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) { SetActiveID(id, window); FocusWindow(window); - if (tab_focus_requested || g.IO.KeyCtrl) { start_text_input = true; @@ -6672,9 +6963,8 @@ bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, c if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); - ItemSize(total_bb, style.FramePadding.y); - // Actual slider behavior + render grab + ItemSize(total_bb, style.FramePadding.y); const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. @@ -6703,12 +6993,9 @@ bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(frame_bb, &id)) + if (!ItemAdd(frame_bb, id)) return false; - - const bool hovered = IsHovered(frame_bb, id); - if (hovered) - SetHoveredID(id); + const bool hovered = ItemHoverable(frame_bb, id); if (!display_format) display_format = "%.3f"; @@ -6872,17 +7159,23 @@ bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_s if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio; + float v_cur = g.DragCurrentValue; const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); - if (fabsf(mouse_drag_delta.x - g.DragLastMouseDelta.x) > 0.0f) + float adjust_delta = 0.0f; + //if (g.ActiveIdSource == ImGuiInputSource_Mouse) { - float speed = v_speed; + adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x; if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) - speed = speed * g.DragSpeedScaleFast; + adjust_delta *= g.DragSpeedScaleFast; if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) - speed = speed * g.DragSpeedScaleSlow; + adjust_delta *= g.DragSpeedScaleSlow; + } + adjust_delta *= v_speed; + g.DragLastMouseDelta.x = mouse_drag_delta.x; - float adjust_delta = (mouse_drag_delta.x - g.DragLastMouseDelta.x) * speed; + if (fabsf(adjust_delta) > 0.0f) + { if (fabsf(power - 1.0f) > 0.001f) { // Logarithmic curve on both side of 0.0 @@ -6897,7 +7190,6 @@ bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_s { v_cur += adjust_delta; } - g.DragLastMouseDelta.x = mouse_drag_delta.x; // Clamp if (v_min < v_max) @@ -6939,15 +7231,12 @@ bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, f const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); // NB- we don't call ItemSize() yet because we may turn into a text edit box below - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) { ItemSize(total_bb, style.FramePadding.y); return false; } - - const bool hovered = IsHovered(frame_bb, id); - if (hovered) - SetHoveredID(id); + const bool hovered = ItemHoverable(frame_bb, id); if (!display_format) display_format = "%.3f"; @@ -6955,12 +7244,11 @@ bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, f // Tabbing or CTRL-clicking on Drag turns it into an input box bool start_text_input = false; - const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id); - if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] | g.IO.MouseDoubleClicked[0]))) + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0]))) { SetActiveID(id, window); FocusWindow(window); - if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0]) { start_text_input = true; @@ -7149,8 +7437,9 @@ void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_ge const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, NULL)) + if (!ItemAdd(total_bb, 0)) return; + const bool hovered = ItemHoverable(inner_bb, 0); // Determine scale from values if not specified if (scale_min == FLT_MAX || scale_max == FLT_MAX) @@ -7178,7 +7467,7 @@ void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_ge // Tooltip on hover int v_hovered = -1; - if (IsHovered(inner_bb, 0)) + if (hovered) { const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); const int v_idx = (int)(t * item_count); @@ -7288,7 +7577,7 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) return; // Render @@ -7296,7 +7585,7 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-window->BorderSize, -window->BorderSize)); const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); - RenderFrame(bb.Min, fill_br, GetColorU32(ImGuiCol_PlotHistogram), false, style.FrameRounding); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); // Default displaying the fraction as percentage string, but user can override it char overlay_buf[32]; @@ -7335,7 +7624,7 @@ bool ImGui::Checkbox(const char* label, bool* v) total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); } - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) return false; bool hovered, held; @@ -7348,13 +7637,13 @@ bool ImGui::Checkbox(const char* label, bool* v) { const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); - window->DrawList->AddRectFilled(check_bb.Min+ImVec2(pad,pad), check_bb.Max-ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), style.FrameRounding); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); } if (g.LogEnabled) - LogRenderedText(text_bb.GetTL(), *v ? "[x]" : "[ ]"); + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); if (label_size.x > 0.0f) - RenderText(text_bb.GetTL(), label); + RenderText(text_bb.Min, label); return pressed; } @@ -7398,7 +7687,7 @@ bool ImGui::RadioButton(const char* label, bool active) total_bb.Add(text_bb); } - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) return false; ImVec2 center = check_bb.GetCenter(); @@ -7424,9 +7713,9 @@ bool ImGui::RadioButton(const char* label, bool active) } if (g.LogEnabled) - LogRenderedText(text_bb.GetTL(), active ? "(x)" : "( )"); + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); if (label_size.x > 0.0f) - RenderText(text_bb.GetTL(), label); + RenderText(text_bb.Min, label); return pressed; } @@ -7736,14 +8025,17 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 else { ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) return false; } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; // Password pushes a temporary font with only a fallback glyph if (is_password) { - const ImFont::Glyph* glyph = g.Font->FindGlyph('*'); + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); ImFont* password_font = &g.InputTextPasswordFont; password_font->FontSize = g.Font->FontSize; password_font->Scale = g.Font->Scale; @@ -7752,27 +8044,23 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 password_font->Descent = g.Font->Descent; password_font->ContainerAtlas = g.Font->ContainerAtlas; password_font->FallbackGlyph = glyph; - password_font->FallbackXAdvance = glyph->XAdvance; - IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexXAdvance.empty() && password_font->IndexLookup.empty()); + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); PushFont(password_font); } // NB: we are only allowed to access 'edit_state' if we are the active widget. ImGuiTextEditState& edit_state = g.InputTextState; - const bool focus_requested = FocusableItemRegister(window, g.ActiveId == id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; - const bool hovered = IsHovered(frame_bb, id); - if (hovered) - { - SetHoveredID(id); - g.MouseCursor = ImGuiMouseCursor_TextInput; - } const bool user_clicked = hovered && io.MouseClicked[0]; const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + bool clear_active_id = false; + bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0; if (focus_requested || user_clicked || user_scrolled) { @@ -7818,8 +8106,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 else if (io.MouseClicked[0]) { // Release focus when we click outside - if (g.ActiveId == id) - ClearActiveID(); + clear_active_id = true; } bool value_changed = false; @@ -7842,6 +8129,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. // Down the line we should have a cleaner library-wide concept of Selected vs Active. g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; // Edit in progress const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; @@ -7892,9 +8180,12 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Consume characters memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); } + } - // Handle various key-presses - bool cancel_edit = false; + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_shortcut_key_only = (io.OSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl const bool is_wordmove_key_down = io.OSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl @@ -7921,8 +8212,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) { - ClearActiveID(); - enter_pressed = true; + enter_pressed = clear_active_id = true; } else if (is_editable) { @@ -7937,10 +8227,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (InputTextFilterCharacter(&c, flags, callback, user_data)) edit_state.OnKeyPressed((int)c); } - else if (IsKeyPressedMap(ImGuiKey_Escape)) { ClearActiveID(); cancel_edit = true; } - else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } - else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } - else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } else if (is_shortcut_key_only && !is_password && ((IsKeyPressedMap(ImGuiKey_X) && is_editable) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) { // Cut, Copy @@ -7991,7 +8281,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 ImGui::MemFree(clipboard_filtered); } } + } + if (g.ActiveId == id) + { if (cancel_edit) { // Restore initial value @@ -8001,7 +8294,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 value_changed = true; } } - else + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) { // Apply new value immediately - copy modified buffer back // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer @@ -8089,6 +8386,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } } + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + // Render // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; @@ -8257,7 +8558,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Log as text if (g.LogEnabled && !is_password) - LogRenderedText(render_pos, buf_display, NULL); + LogRenderedText(&render_pos, buf_display, NULL); if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -8279,6 +8580,12 @@ bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, co return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); } +static inline float SmallSquareSize() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + // NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) { @@ -8292,7 +8599,7 @@ bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data BeginGroup(); PushID(label); - const ImVec2 button_sz = ImVec2(g.FontSize, g.FontSize) + style.FramePadding*2.0f; + const ImVec2 button_sz = ImVec2(SmallSquareSize(), SmallSquareSize()); if (step_ptr) PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); @@ -8374,7 +8681,6 @@ bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal } PopID(); - window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); TextUnformatted(label, FindRenderedTextEnd(label)); EndGroup(); @@ -8417,7 +8723,6 @@ bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextF } PopID(); - window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); TextUnformatted(label, FindRenderedTextEnd(label)); EndGroup(); @@ -8488,8 +8793,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa return value_changed; } -// Combo box function. -bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImVec2 popup_size) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) @@ -8504,100 +8808,112 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, &id)) + if (!ItemAdd(total_bb, id)) return false; - const float arrow_size = (g.FontSize + style.FramePadding.x * 2.0f); - const bool hovered = IsHovered(frame_bb, id); + const float arrow_size = SmallSquareSize(); + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING - RenderCollapseTriangle(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y) + style.FramePadding, true); + RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); - if (*current_item >= 0 && *current_item < items_count) - { - const char* item_text; - if (items_getter(data, *current_item, &item_text)) - RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, item_text, NULL, NULL, ImVec2(0.0f,0.0f)); - } + if (preview_value != NULL) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - bool popup_toggled = false; - if (hovered) + if (pressed && !popup_open) { - SetHoveredID(id); - if (g.IO.MouseClicked[0]) - { - ClearActiveID(); - popup_toggled = true; - } - } - if (popup_toggled) - { - if (IsPopupOpen(id)) - { - ClosePopup(id); - } - else - { - FocusWindow(window); - OpenPopup(label); - popup_open = true; - } + OpenPopupEx(id, false); + popup_open = true; } + if (!popup_open) + return false; + + if (popup_size.x == 0.0f) + popup_size.x = w; + + float popup_y1 = frame_bb.Max.y; + float popup_y2 = ImClamp(popup_y1 + popup_size.y, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y); + if ((popup_y2 - popup_y1) < ImMin(popup_size.y, frame_bb.Min.y - style.DisplaySafeAreaPadding.y)) + { + // Position our combo ABOVE because there's more space to fit! (FIXME: Handle in Begin() or use a shared helper. We have similar code in Begin() for popup placement) + popup_y1 = ImClamp(frame_bb.Min.y - popup_size.y, style.DisplaySafeAreaPadding.y, frame_bb.Min.y); + popup_y2 = frame_bb.Min.y; + SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Min.y), ImGuiCond_Always, ImVec2(0.0f, 1.0f)); + } + else + { + // Position our combo below + SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Max.y), ImGuiCond_Always, ImVec2(0.0f, 0.0f)); + } + SetNextWindowSize(ImVec2(popup_size.x, popup_y2 - popup_y1), ImGuiCond_Appearing); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + + const ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); + if (!BeginPopupEx(id, flags)) + { + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + Spacing(); + + return true; +} + +void ImGui::EndCombo() +{ + EndPopup(); + PopStyleVar(); +} + +// Combo box function. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* preview_text = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_text); + + // Size default to hold ~7 items + if (height_in_items < 0) + height_in_items = 7; + float popup_height = (g.FontSize + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3); + + if (!BeginCombo(label, preview_text, ImVec2(0.0f, popup_height))) + return false; + + // Display items + // FIXME-OPT: Use clipper bool value_changed = false; - if (IsPopupOpen(id)) + for (int i = 0; i < items_count; i++) { - // Size default to hold ~7 items - if (height_in_items < 0) - height_in_items = 7; - - float popup_height = (label_size.y + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3); - float popup_y1 = frame_bb.Max.y; - float popup_y2 = ImClamp(popup_y1 + popup_height, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y); - if ((popup_y2 - popup_y1) < ImMin(popup_height, frame_bb.Min.y - style.DisplaySafeAreaPadding.y)) + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) { - // Position our combo ABOVE because there's more space to fit! (FIXME: Handle in Begin() or use a shared helper. We have similar code in Begin() for popup placement) - popup_y1 = ImClamp(frame_bb.Min.y - popup_height, style.DisplaySafeAreaPadding.y, frame_bb.Min.y); - popup_y2 = frame_bb.Min.y; + value_changed = true; + *current_item = i; } - ImRect popup_rect(ImVec2(frame_bb.Min.x, popup_y1), ImVec2(frame_bb.Max.x, popup_y2)); - SetNextWindowPos(popup_rect.Min); - SetNextWindowSize(popup_rect.GetSize()); - PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); - - const ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); - if (BeginPopupEx(id, flags)) - { - // Display items - // FIXME-OPT: Use clipper - Spacing(); - for (int i = 0; i < items_count; i++) - { - PushID((void*)(intptr_t)i); - const bool item_selected = (i == *current_item); - const char* item_text; - if (!items_getter(data, i, &item_text)) - item_text = "*Unknown item*"; - if (Selectable(item_text, item_selected)) - { - ClearActiveID(); - value_changed = true; - *current_item = i; - } - if (item_selected && popup_toggled) - SetScrollHere(); - PopID(); - } - EndPopup(); - } - PopStyleVar(); + if (item_selected && IsWindowAppearing()) + SetScrollHere(); + PopID(); } + + EndCombo(); return value_changed; } @@ -8641,7 +8957,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl bb_with_spacing.Min.y -= spacing_U; bb_with_spacing.Max.x += spacing_R; bb_with_spacing.Max.y += spacing_D; - if (!ItemAdd(bb_with_spacing, &id)) + if (!ItemAdd(bb_with_spacing, id)) { if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) PushColumnClipRect(); @@ -8649,8 +8965,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } ImGuiButtonFlags button_flags = 0; - if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick; - if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnClick|ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease; if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; bool hovered, held; @@ -8676,7 +8992,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); // Automatically close popups - if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) CloseCurrentPopup(); return pressed; } @@ -8791,23 +9107,38 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo return false; ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; ImVec2 label_size = CalcTextSize(label, NULL, true); - ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); - float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame - float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); - bool pressed = Selectable(label, false, ImGuiSelectableFlags_MenuItem | ImGuiSelectableFlags_DrawFillAvailWidth | (enabled ? 0 : ImGuiSelectableFlags_Disabled), ImVec2(w, 0.0f)); - if (shortcut_size.x > 0.0f) + ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { - PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); - PopStyleColor(); + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * (0.20f+0.200f), g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); } - - if (selected) - RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled)); - return pressed; } @@ -8863,7 +9194,7 @@ bool ImGui::BeginMenuBar() window->DC.CursorPos = ImVec2(rect.Min.x + window->DC.MenuBarOffsetX, rect.Min.y);// + g.Style.FramePadding.y); window->DC.LayoutType = ImGuiLayoutType_Horizontal; window->DC.MenuBarAppending = true; - AlignFirstTextHeightToWidgets(); + AlignTextToFramePadding(); return true; } @@ -8907,32 +9238,34 @@ bool ImGui::BeginMenu(const char* label, bool enabled) ImVec2 popup_pos, pos = window->DC.CursorPos; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); float w = label_size.x; pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); PopStyleVar(); - SameLine(); - window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } else { + // Menu inside a menu popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderCollapseTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), false); + RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), ImGuiDir_Right); if (!enabled) PopStyleColor(); } - bool hovered = enabled && IsHovered(window->DC.LastItemRect, id); + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); if (menuset_is_open) g.NavWindow = backed_nav_window; bool want_open = false, want_close = false; - if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + if (window->DC.LayoutType != ImGuiLayoutType_Horizontal) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) { // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. bool moving_within_opened_triangle = false; @@ -8956,13 +9289,20 @@ bool ImGui::BeginMenu(const char* label, bool enabled) want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); } - else if (menu_is_open && pressed && menuset_is_open) // Menu bar: click an open menu again to close it + else { - want_close = true; - want_open = menu_is_open = false; + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } } - else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // menu-bar: first click to open, then hover to open others - want_open = true; + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' want_close = true; if (want_close && IsPopupOpen(id)) @@ -8982,7 +9322,7 @@ bool ImGui::BeginMenu(const char* label, bool enabled) if (menu_is_open) { SetNextWindowPos(popup_pos, ImGuiCond_Always); - ImGuiWindowFlags flags = ImGuiWindowFlags_ShowBorders | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); + ImGuiWindowFlags flags = ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) } @@ -8995,12 +9335,12 @@ void ImGui::EndMenu() } // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. -void ImGui::ColorTooltip(const char* text, const float col[4], ImGuiColorEditFlags flags) +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; 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]); - BeginTooltipEx(true); + BeginTooltipEx(0, true); const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; if (text_end > text) @@ -9019,12 +9359,6 @@ void ImGui::ColorTooltip(const char* text, const float col[4], ImGuiColorEditFla EndTooltip(); } -static inline float ColorSquareSize() -{ - ImGuiContext& g = *GImGui; - return g.FontSize + g.Style.FramePadding.y * 2.0f; -} - static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) { float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; @@ -9058,7 +9392,7 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU continue; int rounding_corners_flags_cell = 0; if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImGuiCorner_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImGuiCorner_TopRight; } - if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImGuiCorner_BottomLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImGuiCorner_BottomRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImGuiCorner_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImGuiCorner_BotRight; } rounding_corners_flags_cell &= rounding_corners_flags; window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); } @@ -9096,14 +9430,14 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(desc_id); - float default_size = ColorSquareSize(); + float default_size = SmallSquareSize(); if (size.x == 0.0f) size.x = default_size; if (size.y == 0.0f) size.y = default_size; const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - ItemSize(bb); - if (!ItemAdd(bb, &id)) + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) return false; bool hovered, held; @@ -9115,15 +9449,23 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl ImVec4 col_without_alpha(col.x, col.y, col.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.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); if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) { - float mid_x = (float)(int)((bb.Min.x + bb.Max.x) * 0.5f + 0.5f); - RenderColorRectWithAlphaCheckerboard(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col), grid_step, ImVec2(-grid_step, 0.0f), rounding, ImGuiCorner_TopRight|ImGuiCorner_BottomRight); - window->DrawList->AddRectFilled(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(col_without_alpha), rounding, ImGuiCorner_TopLeft|ImGuiCorner_BottomLeft); + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImGuiCorner_TopRight|ImGuiCorner_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImGuiCorner_TopLeft|ImGuiCorner_BotLeft); } else { - RenderColorRectWithAlphaCheckerboard(bb.Min, bb.Max, GetColorU32((flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha), grid_step, ImVec2(0,0), rounding); + // 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_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ~0); } if (window->Flags & ImGuiWindowFlags_ShowBorders) RenderFrameBorder(bb.Min, bb.Max, rounding); @@ -9141,28 +9483,52 @@ bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flag return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); } -static void ColorEditOptionsPopup(ImGuiColorEditFlags flags) +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) { bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); - if ((!allow_opt_inputs && !allow_opt_datatype) || !ImGui::BeginPopup("context")) + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; ImGuiContext& g = *GImGui; ImGuiColorEditFlags opts = g.ColorEditOptions; if (allow_opt_inputs) { - if (ImGui::RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; - if (ImGui::RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; - if (ImGui::RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; } if (allow_opt_datatype) { - if (allow_opt_inputs) ImGui::Separator(); - if (ImGui::RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; - if (ImGui::RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + 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]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + g.ColorEditOptions = opts; - ImGui::EndPopup(); + EndPopup(); } static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, float* ref_col) @@ -9174,7 +9540,7 @@ static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, float* ref_col) ImGuiContext& g = *GImGui; if (allow_opt_picker) { - ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ColorSquareSize() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (SmallSquareSize() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function ImGui::PushItemWidth(picker_size.x); for (int picker_type = 0; picker_type < 2; picker_type++) { @@ -9214,7 +9580,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; - const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (ColorSquareSize() + style.ItemInnerSpacing.x); + const float square_sz = SmallSquareSize(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); const float w_items_all = CalcItemWidth() - w_extra; const char* label_display_end = FindRenderedTextEnd(label); @@ -9232,7 +9599,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Context menu: display and modify options (before defaults are applied) if (!(flags & ImGuiColorEditFlags_NoOptions)) - ColorEditOptionsPopup(flags); + ColorEditOptionsPopup(col, flags); // Read stored options if (!(flags & ImGuiColorEditFlags__InputsMask)) @@ -9285,8 +9652,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag value_changed |= value_changed_as_float |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); else value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); } PopItemWidth(); PopItemWidth(); @@ -9312,8 +9679,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag else sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); PopItemWidth(); } @@ -9334,9 +9701,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); } } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); - + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + if (BeginPopup("picker")) { picker_active = true; @@ -9345,7 +9712,6 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag TextUnformatted(label, label_display_end); Separator(); } - float square_sz = ColorSquareSize(); ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? @@ -9415,21 +9781,6 @@ static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); } -static void PaintVertsLinearGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) -{ - ImVec2 gradient_extent = gradient_p1 - gradient_p0; - float gradient_inv_length = ImInvLength(gradient_extent, 0.0f); - for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) - { - float d = ImDot(vert->pos - gradient_p0, gradient_extent); - float t = ImMin(sqrtf(ImMax(d, 0.0f)) * gradient_inv_length, 1.0f); - int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); - int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); - int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); - vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); - } -} - // ColorPicker // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) @@ -9462,7 +9813,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl // Setup bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); ImVec2 picker_pos = window->DC.CursorPos; - float bars_width = ColorSquareSize(); // Arbitrary smallish width of Hue/Alpha picking bars + float square_sz = SmallSquareSize(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; @@ -9516,8 +9868,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed = value_changed_sv = true; } } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); } else if (flags & ImGuiColorEditFlags_PickerHueBar) { @@ -9529,8 +9881,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); value_changed = value_changed_sv = true; } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); // Hue bar logic SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); @@ -9574,7 +9926,6 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); - float square_sz = ColorSquareSize(); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); @@ -9647,7 +9998,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl // Paint colors over existing vertices ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner); ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner); - PaintVertsLinearGradientKeepAlpha(draw_list->_VtxWritePtr - (draw_list->_VtxCurrentIdx - vert_start_idx), draw_list->_VtxWritePtr, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + ShadeVertsLinearColorGradientKeepAlpha(draw_list->_VtxWritePtr - (draw_list->_VtxCurrentIdx - vert_start_idx), draw_list->_VtxWritePtr, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); } // Render Cursor + preview on Hue Wheel @@ -9722,7 +10073,19 @@ void ImGui::Separator() ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = 0; + if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0) + flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator if (window->DC.ColumnsCount > 1) PopClipRect(); @@ -9733,7 +10096,7 @@ void ImGui::Separator() const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. - if (!ItemAdd(bb, NULL)) + if (!ItemAdd(bb, 0)) { if (window->DC.ColumnsCount > 1) PushColumnClipRect(); @@ -9742,9 +10105,8 @@ void ImGui::Separator() window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); - ImGuiContext& g = *GImGui; if (g.LogEnabled) - LogText(IM_NEWLINE "--------------------------------"); + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); if (window->DC.ColumnsCount > 1) { @@ -9753,6 +10115,25 @@ void ImGui::Separator() } } +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + void ImGui::Spacing() { ImGuiWindow* window = GetCurrentWindow(); @@ -9769,7 +10150,7 @@ void ImGui::Dummy(const ImVec2& size) const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(bb); - ItemAdd(bb, NULL); + ItemAdd(bb, 0); } bool ImGui::IsRectVisible(const ImVec2& size) @@ -9833,16 +10214,15 @@ void ImGui::EndGroup() { window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); - ItemAdd(group_bb, NULL); + ItemAdd(group_bb, 0); } - // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will function on the entire group. + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); if (active_id_within_group) window->DC.LastItemId = g.ActiveId; - if (active_id_within_group && g.HoveredId == g.ActiveId) - window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = true; + window->DC.LastItemRect = group_bb; window->DC.GroupStack.pop_back(); @@ -9882,10 +10262,15 @@ void ImGui::NewLine() ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. ItemSize(ImVec2(0,0)); else - ItemSize(ImVec2(0.0f, GImGui->FontSize)); + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; } void ImGui::NextColumn() @@ -9948,7 +10333,7 @@ static float GetDraggedColumnOffset(int column_index) // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. ImGuiContext& g = *GImGui; - ImGuiWindow* window = ImGui::GetCurrentWindowRead(); + ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets. IM_ASSERT(g.ActiveId == window->DC.ColumnsSetId + ImGuiID(column_index)); @@ -10115,7 +10500,7 @@ void ImGui::EndColumns() const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(i); const float column_w = 4.0f; // Width for interaction const ImRect column_rect(ImVec2(x - column_w, y1), ImVec2(x + column_w, y2)); - if (IsClippedEx(column_rect, &column_id, false)) + if (IsClippedEx(column_rect, column_id, false)) continue; bool hovered = false, held = false; @@ -10249,14 +10634,14 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) // PLATFORM DEPENDENT HELPERS //----------------------------------------------------------------------------- -#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS)) +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #endif // Win32 API clipboard implementation -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) #ifdef _MSC_VER #pragma comment(lib, "user32") @@ -10292,7 +10677,10 @@ static void SetClipboardTextFn_DefaultImpl(void*, const char* text) const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); if (wbuf_handle == NULL) + { + CloseClipboard(); return; + } ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); GlobalUnlock(wbuf_handle); @@ -10316,7 +10704,7 @@ static void SetClipboardTextFn_DefaultImpl(void*, const char* text) ImGuiContext& g = *GImGui; g.PrivateClipboard.clear(); const char* text_end = text + strlen(text); - g.PrivateClipboard.resize((size_t)(text_end - text) + 1); + g.PrivateClipboard.resize((int)(text_end - text) + 1); memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); g.PrivateClipboard[(int)(text_end - text)] = 0; } @@ -10324,7 +10712,7 @@ static void SetClipboardTextFn_DefaultImpl(void*, const char* text) #endif // Win32 API IME support (for Asian languages, etc.) -#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) #include #ifdef _MSC_VER @@ -10387,6 +10775,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) int elem_offset = 0; for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; if (pcmd->UserCallback) { ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); @@ -10409,13 +10799,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) while (clipper.Step()) for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) { - char buf[300], *buf_p = buf; + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); ImVec2 triangles_pos[3]; for (int n = 0; n < 3; n++, vtx_i++) { ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; triangles_pos[n] = v.pos; - buf_p += sprintf(buf_p, "%s %04d { pos = (%8.2f,%8.2f), uv = (%.6f,%.6f), col = %08X }\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d { pos = (%8.2f,%8.2f), uv = (%.6f,%.6f), col = %08X }\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); } ImGui::Selectable(buf, false); if (ImGui::IsItemHovered()) @@ -10441,8 +10832,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) return; NodeDrawList(window->DrawList, "DrawList"); - ImGui::BulletText("Pos: (%.1f,%.1f)", window->Pos.x, window->Pos.y); - ImGui::BulletText("Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + if (ImGui::IsItemHovered()) + GImGui->OverlayDrawList.AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255,255,0,255)); ImGui::BulletText("Scroll: (%.2f,%.2f)", window->Scroll.x, window->Scroll.y); ImGui::BulletText("Active: %d, Accessed: %d", window->Active, window->Accessed); if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); @@ -10456,8 +10848,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) Funcs::NodeWindows(g.Windows, "Windows"); if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.RenderDrawLists[0].Size)) { - for (int i = 0; i < g.RenderDrawLists[0].Size; i++) - Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList"); + for (int layer = 0; layer < IM_ARRAYSIZE(g.RenderDrawLists); layer++) + for (int i = 0; i < g.RenderDrawLists[layer].Size; i++) + Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList"); ImGui::TreePop(); } if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) diff --git a/imgui/imgui.h b/imgui/imgui.h index 27a11fcf..3dafbe92 100644 --- a/imgui/imgui.h +++ b/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.51 +// dear imgui, v1.52 // (headers) // See imgui.cpp file for documentation. @@ -16,7 +16,7 @@ #include // ptrdiff_t, NULL #include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp -#define IMGUI_VERSION "1.51" +#define IMGUI_VERSION "1.52" // Define attributes of all API symbols declarations, e.g. for DLL under Windows. #ifndef IMGUI_API @@ -29,12 +29,16 @@ #define IM_ASSERT(_EXPR) assert(_EXPR) #endif +// Helpers // Some compilers support applying printf-style warnings to user functions. #if defined(__clang__) || defined(__GNUC__) -#define IM_PRINTFARGS(FMT) __attribute__((format(printf, FMT, (FMT+1)))) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) #else -#define IM_PRINTFARGS(FMT) +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) #endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) #if defined(__clang__) #pragma clang diagnostic push @@ -67,17 +71,18 @@ typedef unsigned int ImU32; // 32-bit unsigned integer (typically used t typedef unsigned int ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) typedef unsigned short ImWchar; // character for keyboard input/display typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) -typedef int ImGuiCol; // a color identifier for styling // enum ImGuiCol_ -typedef int ImGuiStyleVar; // a variable identifier for styling // enum ImGuiStyleVar_ -typedef int ImGuiKey; // a key identifier (ImGui-side enum) // enum ImGuiKey_ -typedef int ImGuiColorEditFlags; // color edit flags for Color*() // enum ImGuiColorEditFlags_ -typedef int ImGuiMouseCursor; // a mouse cursor identifier // enum ImGuiMouseCursor_ -typedef int ImGuiWindowFlags; // window flags for Begin*() // enum ImGuiWindowFlags_ -typedef int ImGuiCond; // condition flags for Set*() // enum ImGuiCond_ -typedef int ImGuiColumnsFlags; // flags for *Columns*() // enum ImGuiColumnsFlags_ -typedef int ImGuiInputTextFlags; // flags for InputText*() // enum ImGuiInputTextFlags_ -typedef int ImGuiSelectableFlags; // flags for Selectable() // enum ImGuiSelectableFlags_ -typedef int ImGuiTreeNodeFlags; // flags for TreeNode*(), Collapsing*() // enum ImGuiTreeNodeFlags_ +typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ +typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ +typedef int ImGuiColorEditFlags; // flags: color edit flags for Color*() // enum ImGuiColorEditFlags_ +typedef int ImGuiWindowFlags; // flags: window flags for Begin*() // enum ImGuiWindowFlags_ +typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ +typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(), Collapsing*() // enum ImGuiTreeNodeFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() // enum ImGuiHoveredFlags_ typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); typedef void (*ImGuiSizeConstraintCallback)(ImGuiSizeConstraintCallbackData* data); #ifdef _MSC_VER @@ -130,7 +135,6 @@ namespace ImGui // Window IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // push window to the stack and start appending to it. see .cpp for details. return false when window is collapsed, so you can early out in your code. 'bool* p_open' creates a widget on the upper-right to close the window (which sets your bool to false). - IMGUI_API bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha = -1.0f, ImGuiWindowFlags flags = 0); // OBSOLETE. this is the older/longer API. the extra parameters aren't very relevant. call SetNextWindowSize() instead if you want to set a window size. For regular windows, 'size_on_first_use' only applies to the first time EVER the window is created and probably not what you want! might obsolete this API eventually. IMGUI_API void End(); // finish appending to current window, pop it off the window stack. IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0); // begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0); // " @@ -147,10 +151,10 @@ namespace ImGui IMGUI_API float GetWindowWidth(); IMGUI_API float GetWindowHeight(); IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowAppearing(); IMGUI_API void SetWindowFontScale(float scale); // per-window font scale. Adjust IO.FontGlobalScale if you want to scale all windows - IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // set next window position. call before Begin() - IMGUI_API void SetNextWindowPosCenter(ImGuiCond cond = 0); // set next window position to be centered on screen. call before Begin() + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (enforce the range of scrollbars). set axis to 0.0f to leave it automatic. call before Begin() @@ -174,7 +178,7 @@ namespace ImGui IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. - IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use negative 'offset' to access previous widgets. + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) IMGUI_API ImGuiStorage* GetStateStorage(); @@ -201,13 +205,13 @@ namespace ImGui IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space IMGUI_API void PopTextWrapPos(); - IMGUI_API void PushAllowKeyboardFocus(bool v); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets IMGUI_API void PopAllowKeyboardFocus(); - IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (uses io.KeyRepeatDelay/io.KeyRepeatRate for now). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. IMGUI_API void PopButtonRepeat(); // Cursor / Layout - IMGUI_API void Separator(); // horizontal line + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally IMGUI_API void NewLine(); // undo a SameLine() IMGUI_API void Spacing(); // add vertical spacing @@ -225,7 +229,7 @@ namespace ImGui IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] - IMGUI_API void AlignFirstTextHeightToWidgets(); // call once if the first item on the line is a Text() item and you want to vertically lower it to match subsequent (bigger) widgets + IMGUI_API void AlignTextToFramePadding(); // vertically align/lower upcoming text to FramePadding.y so that it will aligns to upcoming widgets (call if you have text on a line before regular widgets) IMGUI_API float GetTextLineHeight(); // height of font == GetWindowFontSize() IMGUI_API float GetTextLineHeightWithSpacing(); // distance (in pixels) between 2 consecutive lines of text == GetWindowFontSize() + GetStyle().ItemSpacing.y IMGUI_API float GetItemsLineHeightWithSpacing(); // distance (in pixels) between 2 consecutive lines of standard height widgets == GetWindowFontSize() + GetStyle().FramePadding.y*2 + GetStyle().ItemSpacing.y @@ -233,43 +237,45 @@ namespace ImGui // Columns // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking. IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); - IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished - IMGUI_API int GetColumnIndex(); // get current column index - IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column - IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column - IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f - IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column IMGUI_API int GetColumnsCount(); // ID scopes - // If you are creating widgets in a loop you most likely want to push a unique identifier so ImGui can differentiate them. + // If you are creating widgets in a loop you most likely want to push a unique identifier (e.g. object pointer, loop index) so ImGui can differentiate them. // You can also use the "##foobar" syntax within widget label to distinguish them from each others. Read "A primer on the use of labels/IDs" in the FAQ for more details. - IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the *entire* stack! + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); IMGUI_API void PushID(const void* ptr_id); IMGUI_API void PushID(int int_id); IMGUI_API void PopID(); - IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). useful if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); IMGUI_API ImGuiID GetID(const void* ptr_id); - // Widgets - IMGUI_API void Text(const char* fmt, ...) IM_PRINTFARGS(1); - IMGUI_API void TextV(const char* fmt, va_list args); - IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_PRINTFARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); - IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args); - IMGUI_API void TextDisabled(const char* fmt, ...) IM_PRINTFARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); - IMGUI_API void TextDisabledV(const char* fmt, va_list args); - IMGUI_API void TextWrapped(const char* fmt, ...) IM_PRINTFARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). - IMGUI_API void TextWrappedV(const char* fmt, va_list args); - IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // doesn't require null terminated string if 'text_end' is specified. no copy done to any bounded stack buffer, recommended for long chunks of text - IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_PRINTFARGS(2); // display text+label aligned the same way as value+label widgets - IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args); - IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses - IMGUI_API void BulletText(const char* fmt, ...) IM_PRINTFARGS(1); // shortcut for Bullet()+Text() - IMGUI_API void BulletTextV(const char* fmt, va_list args); + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // doesn't require null terminated string if 'text_end' is specified. no copy done, no limits, recommended for long chunks of text + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Main IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button - IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed in text + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding @@ -335,15 +341,15 @@ namespace ImGui // Widgets: Trees IMGUI_API bool TreeNode(const char* label); // if returning 'true' the node is open and the tree id is pushed into the id stack. user is responsible for calling TreePop(). - IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_PRINTFARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). - IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_PRINTFARGS(2); // " - IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args); // " - IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args); // " + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); - IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_PRINTFARGS(3); - IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_PRINTFARGS(3); - IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args); - IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); IMGUI_API void TreePush(const char* str_id = NULL); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call Push/Pop yourself for layout purpose IMGUI_API void TreePush(const void* ptr_id = NULL); // " IMGUI_API void TreePop(); // ~ Unindent()+PopId() @@ -369,15 +375,15 @@ namespace ImGui IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); // Tooltips - IMGUI_API void SetTooltip(const char* fmt, ...) IM_PRINTFARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). - IMGUI_API void SetTooltipV(const char* fmt, va_list args); + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of contents). IMGUI_API void EndTooltip(); // Menus IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. only call EndMainMenuBar() if this returns true! IMGUI_API void EndMainMenuBar(); - IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set). only call EndMenuBar() if this returns true! + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). only call EndMenuBar() if this returns true! IMGUI_API void EndMenuBar(); IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! IMGUI_API void EndMenu(); @@ -386,11 +392,12 @@ namespace ImGui // Popups IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. IMGUI_API bool BeginPopup(const char* str_id); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returned true! IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags extra_flags = 0); // modal dialog (block interactions behind the modal window, can't close the modal window by clicking outside) - IMGUI_API bool BeginPopupContextItem(const char* str_id, int mouse_button = 1); // helper to open and begin popup when clicked on last item. read comments in .cpp! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. - IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (no window). + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). IMGUI_API void EndPopup(); IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. @@ -401,18 +408,20 @@ namespace ImGui IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard IMGUI_API void LogFinish(); // stop logging (close file, etc.) IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard - IMGUI_API void LogText(const char* fmt, ...) IM_PRINTFARGS(1); // pass text data straight to log (without being displayed) + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) // Clipping IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); IMGUI_API void PopClipRect(); + // Styles + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); + // Utilities - IMGUI_API bool IsItemHovered(); // was the last item hovered by mouse? - IMGUI_API bool IsItemRectHovered(); // was the last item hovered by mouse? even if another item is active or window is blocked by popup while we are hovering this - IMGUI_API bool IsItemActive(); // was the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) - IMGUI_API bool IsItemClicked(int mouse_button = 0); // was the last item clicked? (e.g. button/node just clicked on) - IMGUI_API bool IsItemVisible(); // was the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered by mouse (and usable)? + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) + IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) IMGUI_API bool IsAnyItemHovered(); IMGUI_API bool IsAnyItemActive(); IMGUI_API ImVec2 GetItemRectMin(); // get bounding rect of last item in screen space @@ -420,11 +429,10 @@ namespace ImGui IMGUI_API ImVec2 GetItemRectSize(); // " IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. IMGUI_API bool IsWindowFocused(); // is current window focused - IMGUI_API bool IsWindowHovered(); // is current window hovered and hoverable (not blocked by a popup) (differentiate child windows from each others) - IMGUI_API bool IsWindowRectHovered(); // is current window rectnagle hovered, disregarding of any consideration of being blocked by a popup. (unlike IsWindowHovered() this will return true even if the window is blocked because of a popup) + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal) IMGUI_API bool IsRootWindowFocused(); // is current root window focused (root = top-most parent of a child, otherwise self) IMGUI_API bool IsRootWindowOrAnyChildFocused(); // is current root window or any of its child (including current window) focused - IMGUI_API bool IsRootWindowOrAnyChildHovered(); // is current root window or any of its child (including current window) hovered and hoverable (not blocked by a popup) + IMGUI_API bool IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags = 0); // is current root window or any of its child (including current window) hovered and hoverable (not blocked by a popup) IMGUI_API bool IsAnyWindowHovered(); // is mouse hovering any visible window IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. @@ -448,12 +456,14 @@ namespace ImGui IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate IMGUI_API bool IsMouseDown(int button); // is mouse button held IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings. disregarding of consideration of focus/window ordering/blocked by a popup. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse positioning at the time of opening popup we have BeginPopup() into IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold @@ -477,20 +487,6 @@ namespace ImGui IMGUI_API ImGuiContext* GetCurrentContext(); IMGUI_API void SetCurrentContext(ImGuiContext* ctx); - // Obsolete functions (Will be removed! Also see 'API BREAKING CHANGES' section in imgui.cpp) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - static inline bool IsItemHoveredRect() { return IsItemRectHovered(); } // OBSOLETE 1.51+ - static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETE 1.51+. This was partly broken. You probably wanted to use ImGui::GetIO().WantCaptureMouse instead. - static inline bool IsMouseHoveringAnyWindow() { return IsAnyWindowHovered(); } // OBSOLETE 1.51+ - static inline bool IsMouseHoveringWindow() { return IsWindowRectHovered(); } // OBSOLETE 1.51+ - static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1<<5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } // OBSOLETE 1.49+ - static inline ImFont* GetWindowFont() { return GetFont(); } // OBSOLETE 1.48+ - static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETE 1.48+ - static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETE 1.42+ - static inline bool GetWindowCollapsed() { return ImGui::IsWindowCollapsed(); } // OBSOLETE 1.39+ - static inline bool IsRectClipped(const ImVec2& size) { return !IsRectVisible(size); } // OBSOLETE 1.39+ -#endif - } // namespace ImGui // Flags for ImGui::Begin() @@ -506,7 +502,7 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file - ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs + ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state @@ -560,8 +556,9 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow - //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 10, // FIXME: TODO: Extend hit box horizontally even if not framed - //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 11, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog }; @@ -574,6 +571,17 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too }; +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 0, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 1, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 2, // Return true even if an active item is blocking access to this item/window + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 3, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped +}; + // User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array enum ImGuiKey_ { @@ -613,8 +621,8 @@ enum ImGuiCol_ ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive, ImGuiCol_TitleBg, - ImGuiCol_TitleBgCollapsed, ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, ImGuiCol_MenuBarBg, ImGuiCol_ScrollbarBg, ImGuiCol_ScrollbarGrab, @@ -718,14 +726,14 @@ enum ImGuiMouseCursor_ ImGuiMouseCursor_Count_ }; -// Condition flags for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions -// All those functions treat 0 as a shortcut to ImGuiCond_Always +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// All those functions treat 0 as a shortcut to ImGuiCond_Always. From the point of view of the user use this as an enum (don't combine multiple values into flags). enum ImGuiCond_ { - ImGuiCond_Always = 1 << 0, // Set the variable - ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) - ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) - ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -761,6 +769,7 @@ struct ImGuiStyle ImVec4 Colors[ImGuiCol_COUNT]; IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); }; // This is where your app communicate with ImGui. Access via ImGui::GetIO(). @@ -781,7 +790,7 @@ struct ImGuiIO float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). - float KeyRepeatRate; // = 0.020f // When holding a key/button, rate at which it repeats, in seconds. + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. void* UserData; // = NULL // Store your own data for retrieval by callbacks. ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. @@ -824,7 +833,7 @@ struct ImGuiIO // Input - Fill before calling NewFrame() //------------------------------------------------------------------ - ImVec2 MousePos; // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) bool MouseDown[5]; // Mouse buttons: left, right, middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. float MouseWheel; // Mouse wheel: 1 unit scrolls about 5 lines text. bool MouseDrawCursor; // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). @@ -847,6 +856,7 @@ struct ImGuiIO bool WantCaptureMouse; // Mouse is hovering a window or widget is active (= ImGui will use your mouse input). Use to hide mouse from the rest of your application bool WantCaptureKeyboard; // Widget is active (= ImGui will use your keyboard input). Use to hide keyboard from the rest of your application bool WantTextInput; // Some text input widget is active, which will read input characters from the InputCharacters array. Use to activate on screen keyboard if your system needs one + bool WantMoveMouse; // [BETA-NAV] MousePos has been altered. back-end should reposition mouse on next frame. used only if 'NavMovesMouse=true'. float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames int MetricsAllocs; // Number of active memory allocations int MetricsRenderVertices; // Vertices output during last call to Render() @@ -855,13 +865,13 @@ struct ImGuiIO ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are negative, so a disappearing/reappearing mouse won't have a huge delta for one frame. //------------------------------------------------------------------ - // [Private] ImGui will maintain those fields. Forward compatibility not guaranteed! + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! //------------------------------------------------------------------ ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) - bool MouseClicked[5]; // Mouse button went from !Down to Down ImVec2 MouseClickedPos[5]; // Position at time of clicking float MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? bool MouseReleased[5]; // Mouse button went from Down to !Down bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. @@ -874,6 +884,27 @@ struct ImGuiIO IMGUI_API ImGuiIO(); }; +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Also see 'API BREAKING CHANGES' section in imgui.cpp) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // OBSOLETE 1.52+. use SetNextWindowSize() instead if you want to set a window size. + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETE 1.52+ + static inline void SetNextWindowPosCenter(ImGuiCond cond = 0) { SetNextWindowPos(ImVec2(GetIO().DisplaySize.x * 0.5f, GetIO().DisplaySize.y * 0.5f), cond, ImVec2(0.5f, 0.5f)); } // OBSOLETE 1.52+ + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } // OBSOLETE 1.51+ + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETE 1.51+. This was partly broken. You probably wanted to use ImGui::GetIO().WantCaptureMouse instead. + static inline bool IsMouseHoveringAnyWindow() { return IsAnyWindowHovered(); } // OBSOLETE 1.51+ + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } // OBSOLETE 1.51+ + static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } // OBSOLETE 1.49+ + static inline ImFont* GetWindowFont() { return GetFont(); } // OBSOLETE 1.48+ + static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETE 1.48+ + static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETE 1.42+ +} +#endif + //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- @@ -916,12 +947,13 @@ public: inline int _grow_capacity(int size) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > size ? new_capacity : size; } inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) Data[n] = v; Size = new_size; } inline void reserve(int new_capacity) { if (new_capacity <= Capacity) return; - T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(value_type)); + T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(T)); if (Data) - memcpy(new_data, Data, (size_t)Size * sizeof(value_type)); + memcpy(new_data, Data, (size_t)Size * sizeof(T)); ImGui::MemFree(Data); Data = new_data; Capacity = new_capacity; @@ -996,8 +1028,8 @@ struct ImGuiTextBuffer bool empty() { return Buf.Size <= 1; } void clear() { Buf.clear(); Buf.push_back(0); } const char* c_str() const { return Buf.Data; } - IMGUI_API void append(const char* fmt, ...) IM_PRINTFARGS(2); - IMGUI_API void appendv(const char* fmt, va_list args); + IMGUI_API void append(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendv(const char* fmt, va_list args) IM_FMTLIST(2); }; // Helper: Simple Key->value storage @@ -1317,15 +1349,17 @@ struct ImFontConfig { void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size - bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). Set to true + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). int FontNo; // 0 // Index of font within TTF/OTF file - float SizePixels; // // Size in pixels for rasterizer + float SizePixels; // // Size in pixels for rasterizer. int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input - const ImWchar* GlyphRanges; // // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + ImVec2 GlyphExtraSpacing; // 1, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. // [Internal] char Name[32]; // Name (strictly to ease debugging) @@ -1334,6 +1368,14 @@ struct ImFontConfig IMGUI_API ImFontConfig(); }; +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + // Load and rasterize multiple TTF/OTF fonts into a same texture. // Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. // We also add custom graphic data into the texture that serves for ImGui. @@ -1357,25 +1399,29 @@ struct ImFontAtlas IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) IMGUI_API void Clear(); // Clear all - // Retrieve texture data - // User is in charge of copying the pixels into graphics memory, then call SetTextureUserID() - // After loading the texture into your graphic system, store your texture handle in 'TexID' (ignore if you aren't using multiple fonts nor images) - // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white, so 75% of the memory is wasted. + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // RGBA32 format is provided for convenience and compatibility, but note that unless you use CustomRect to draw color data, the RGB pixels emitted from Fonts will all be white (~75% of waste). // Pitch = Width * BytesPerPixels + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - void SetTexID(ImTextureID id) { TexID = id; } + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Default + Japanese + full set of about 21000 CJK Unified Ideographs IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters - // Helpers to build glyph ranges from text data. Feed all your application strings/characters to it then call BuildRanges(). + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). struct GlyphRangesBuilder { ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) @@ -1388,57 +1434,63 @@ struct ImFontAtlas IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + //------------------------------------------- // Members - // (Access texture data via GetTexData*() calls which will setup a default font for you.) + //------------------------------------------- + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 int TexWidth; // Texture width calculated during Build(). int TexHeight; // Texture height calculated during Build(). - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. - int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel 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. - - // [Private] User rectangle for packing custom texture data into the atlas. - struct CustomRect - { - unsigned int ID; // Input // User ID. <0x10000 for font mapped data (WIP/UNSUPPORTED), >=0x10000 for other texture data - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned short X, Y; // Output // Packed position in Atlas - CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; } - bool IsPacked() const { return X != 0xFFFF; } - }; - - // [Private] Members ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector ConfigData; // Internal data - IMGUI_API bool Build(); // Build pixels data. This is automatically for you by the GetTexData*** functions. - IMGUI_API int CustomRectRegister(unsigned int id, int width, int height); - IMGUI_API void CustomRectCalcUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList }; // Font runtime data and rendering // ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). struct ImFont { - struct Glyph - { - ImWchar Codepoint; - float XAdvance; - float X0, Y0, X1, Y1; - float U0, V0, U1, V1; // Texture coordinates - }; - // Members: Hot ~62/78 bytes float FontSize; // // Height of characters, set during loading (don't change after loading) float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.f,1.f) // Offset font rendering by xx pixels - ImVector Glyphs; // // All glyphs. - ImVector IndexXAdvance; // // Sparse. Glyphs->XAdvance in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. - const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) - float FallbackXAdvance; // == FallbackGlyph->XAdvance + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() // Members: Cold ~18/26 bytes @@ -1451,11 +1503,11 @@ struct ImFont // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API void Clear(); + IMGUI_API void ClearOutputData(); IMGUI_API void BuildLookupTable(); - IMGUI_API const Glyph* FindGlyph(ImWchar c) const; + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; IMGUI_API void SetFallbackChar(ImWchar c); - float GetCharAdvance(ImWchar c) const { return ((int)c < IndexXAdvance.Size) ? IndexXAdvance[(int)c] : FallbackXAdvance; } + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } bool IsLoaded() const { return ContainerAtlas != NULL; } // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. @@ -1465,9 +1517,14 @@ struct ImFont IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; - // Private + // [Internal] IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif }; #if defined(__clang__) diff --git a/imgui/imgui_demo.cpp b/imgui/imgui_demo.cpp index 8cdb5f2f..ccbf1705 100644 --- a/imgui/imgui_demo.cpp +++ b/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.51 +// dear imgui, v1.52 // (demo code) // Message to the person tempted to delete this file when integrating ImGui into their code base: @@ -260,7 +260,6 @@ void ImGui::ShowTestWindow(bool* p_open) if (ImGui::CollapsingHeader("Widgets")) { - if (ImGui::TreeNode("Basic")) { static int clicked = 0; @@ -430,7 +429,8 @@ void ImGui::ShowTestWindow(bool* p_open) else { // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). - ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, "Selectable Leaf %d", i); + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); if (ImGui::IsItemClicked()) node_clicked = i; } @@ -454,14 +454,16 @@ void ImGui::ShowTestWindow(bool* p_open) if (ImGui::TreeNode("Collapsing Headers")) { static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); if (ImGui::CollapsingHeader("Header")) { - ImGui::Checkbox("Enable extra group", &closable_group); + ImGui::Text("IsItemHovered: %d", IsItemHovered()); for (int i = 0; i < 5; i++) ImGui::Text("Some content %d", i); } if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); for (int i = 0; i < 5; i++) ImGui::Text("More content %d", i); } @@ -809,7 +811,7 @@ void ImGui::ShowTestWindow(bool* p_open) } ImGui::Text("Color button only:"); - ImGui::ColorButton("MyColor##3b", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); ImGui::Text("Color picker:"); static bool alpha = true; @@ -1054,7 +1056,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); // Button - ImGui::AlignFirstTextHeightToWidgets(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Normal buttons"); ImGui::SameLine(); ImGui::Button("Banana"); ImGui::SameLine(); ImGui::Button("Apple"); ImGui::SameLine(); @@ -1127,11 +1129,11 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Button("CCC"); ImGui::Button("DDD"); ImGui::EndGroup(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Group hovered"); ImGui::SameLine(); ImGui::Button("EEE"); ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); } // Capture the group size and create widgets using the same size ImVec2 size = ImGui::GetItemRectSize(); @@ -1180,11 +1182,12 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Text("TEST"); ImGui::SameLine(); ImGui::SmallButton("TEST##2"); - ImGui::AlignFirstTextHeightToWidgets(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); ImGui::Button("Widget##1"); ImGui::SameLine(); ImGui::Text("Widget"); ImGui::SameLine(); - ImGui::SmallButton("Widget##2"); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); // Tree const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; @@ -1192,7 +1195,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::SameLine(0.0f, spacing); if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data - ImGui::AlignFirstTextHeightToWidgets(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data @@ -1202,7 +1205,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::SameLine(0.0f, spacing); ImGui::BulletText("Bullet text"); - ImGui::AlignFirstTextHeightToWidgets(); + ImGui::AlignTextToFramePadding(); ImGui::BulletText("Node"); ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); @@ -1380,41 +1383,32 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::EndPopup(); } - ImGui::Spacing(); - ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); - ImGui::Separator(); - // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. - // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here - // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. - ImGui::PushID("foo"); - ImGui::MenuItem("Menu item", "CTRL+M"); - if (ImGui::BeginMenu("Menu inside a regular window")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::PopID(); - ImGui::Separator(); - ImGui::TreePop(); } if (ImGui::TreeNode("Context menus")) { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseClicked(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. static float value = 0.5f; ImGui::Text("Value = %.3f (<-- right-click here)", value); if (ImGui::BeginPopupContextItem("item context menu")) { if (ImGui::Selectable("Set to zero")) value = 0.0f; if (ImGui::Selectable("Set to PI")) value = 3.1415f; - ImGui::DragFloat("Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); ImGui::EndPopup(); } static char name[32] = "Label1"; - char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceeding label + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label ImGui::Button(buf); - if (ImGui::BeginPopupContextItem("rename context menu")) + if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). { ImGui::Text("Edit name:"); ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); @@ -1435,7 +1429,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::OpenPopup("Delete?"); if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); ImGui::Separator(); //static int dummy_i = 0; @@ -1459,6 +1453,8 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); static int item = 1; ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal if (ImGui::Button("Add another modal..")) ImGui::OpenPopup("Stacked 2"); @@ -1477,6 +1473,25 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::TreePop(); } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } } if (ImGui::CollapsingHeader("Columns")) @@ -1506,7 +1521,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Text("ID"); ImGui::NextColumn(); ImGui::Text("Name"); ImGui::NextColumn(); ImGui::Text("Path"); ImGui::NextColumn(); - ImGui::Text("Flags"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); ImGui::Separator(); const char* names[3] = { "One", "Two", "Three" }; const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; @@ -1517,10 +1532,11 @@ void ImGui::ShowTestWindow(bool* p_open) sprintf(label, "%04d", i); if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) selected = i; + bool hovered = ImGui::IsItemHovered(); ImGui::NextColumn(); ImGui::Text(names[i]); ImGui::NextColumn(); ImGui::Text(paths[i]); ImGui::NextColumn(); - ImGui::Text("...."); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); } ImGui::Columns(1); ImGui::Separator(); @@ -1550,9 +1566,9 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category A")) ImGui::Text("Blah blah blah"); ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category B")) ImGui::Text("Blah blah blah"); ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category C")) ImGui::Text("Blah blah blah"); ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); ImGui::Columns(1); ImGui::Separator(); ImGui::TreePop(); @@ -1677,6 +1693,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse); if (ImGui::TreeNode("Keyboard & Mouse State")) { @@ -1748,6 +1765,34 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::TreePop(); } + if (ImGui::TreeNode("Hovering")) + { + // Testing IsWindowHovered() function + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::TreePop(); + } + if (ImGui::TreeNode("Dragging")) { ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); @@ -1847,13 +1892,13 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::LogToClipboard(); else ImGui::LogToTTY(); - ImGui::LogText("ImGuiStyle& style = ImGui::GetStyle();" IM_NEWLINE); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { const ImVec4& col = style.Colors[i]; const char* name = ImGui::GetStyleColorName(i); if (!output_only_modified || memcmp(&col, (ref ? &ref->Colors[i] : &default_style.Colors[i]), sizeof(ImVec4)) != 0) - ImGui::LogText("style.Colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 22 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); } ImGui::LogFinish(); } @@ -1906,6 +1951,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) for (int i = 0; i < atlas->Fonts.Size; i++) { ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; if (font_details_opened) @@ -1926,7 +1972,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { // Display all glyphs of the fonts in separate pages of 256 characters - const ImFont::Glyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. + const ImFontGlyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. font->FallbackGlyph = NULL; for (int base = 0; base < 0x10000; base += 256) { @@ -1943,7 +1989,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size.x + cell_spacing), base_pos.y + (n / 16) * (cell_size.y + cell_spacing)); ImVec2 cell_p2(cell_p1.x + cell_size.x, cell_p1.y + cell_size.y); - const ImFont::Glyph* glyph = font->FindGlyph((ImWchar)(base+n));; + const ImFontGlyph* glyph = font->FindGlyph((ImWchar)(base+n));; draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); font->RenderChar(draw_list, cell_size.x, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) @@ -1951,7 +1997,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::BeginTooltip(); ImGui::Text("Codepoint: U+%04X", base+n); ImGui::Separator(); - ImGui::Text("XAdvance+1: %.1f", glyph->XAdvance); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); ImGui::EndTooltip(); @@ -1966,6 +2012,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) } ImGui::TreePop(); } + ImGui::PopID(); } static float window_scale = 1.0f; ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window @@ -2105,28 +2152,40 @@ static void ShowExampleAppConstrainedResize(bool* p_open) "Custom: Fixed Steps (100)", }; ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); - if (ImGui::Button("200x200")) ImGui::SetWindowSize(ImVec2(200,200)); ImGui::SameLine(); - if (ImGui::Button("500x500")) ImGui::SetWindowSize(ImVec2(500,500)); ImGui::SameLine(); - if (ImGui::Button("800x200")) ImGui::SetWindowSize(ImVec2(800,200)); + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200,200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500,500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800,200)); } for (int i = 0; i < 10; i++) ImGui::Text("Hello, sailor! Making this line long enough for the example."); } ImGui::End(); } -// Demonstrate creating a simple static window with no decoration. +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. static void ShowExampleAppFixedOverlay(bool* p_open) { - ImGui::SetNextWindowPos(ImVec2(10,10)); - if (!ImGui::Begin("Example: Fixed Overlay", p_open, ImVec2(0,0), 0.3f, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); // Transparent background + if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) { + ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)"); + ImGui::Separator(); + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + ImGui::EndPopup(); + } ImGui::End(); - return; } - ImGui::Text("Simple overlay\non the top-left side of the screen."); - ImGui::Separator(); - ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); - ImGui::End(); + ImGui::PopStyleColor(); } // Demonstrate using "##" and "###" in identifiers to manipulate ID generation. @@ -2298,8 +2357,9 @@ struct ExampleAppConsole ScrollToBottom = true; } - void AddLog(const char* fmt, ...) IM_PRINTFARGS(2) + void AddLog(const char* fmt, ...) IM_FMTARGS(2) { + // FIXME-OPT char buf[1024]; va_list args; va_start(args, fmt); @@ -2325,8 +2385,8 @@ struct ExampleAppConsole // TODO: display items starting from the bottom if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); - if (ImGui::SmallButton("Add Dummy Error")) AddLog("[error] something went wrong"); ImGui::SameLine(); - if (ImGui::SmallButton("Clear")) ClearLog(); ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } @@ -2385,7 +2445,7 @@ struct ExampleAppConsole if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) { char* input_end = InputBuf+strlen(InputBuf); - while (input_end > InputBuf && input_end[-1] == ' ') input_end--; *input_end = 0; + while (input_end > InputBuf && input_end[-1] == ' ') { input_end--; } *input_end = 0; if (InputBuf[0]) ExecCommand(InputBuf); strcpy(InputBuf, ""); @@ -2426,7 +2486,8 @@ struct ExampleAppConsole } else if (Stricmp(command_line, "HISTORY") == 0) { - for (int i = History.Size >= 10 ? History.Size - 10 : 0; i < History.Size; i++) + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) AddLog("%3d: %s\n", i, History[i]); } else @@ -2560,7 +2621,7 @@ struct ExampleAppLog void Clear() { Buf.clear(); LineOffsets.clear(); } - void AddLog(const char* fmt, ...) IM_PRINTFARGS(2) + void AddLog(const char* fmt, ...) IM_FMTARGS(2) { int old_size = Buf.size(); va_list args; @@ -2696,10 +2757,10 @@ static void ShowExampleAppPropertyEditor(bool* p_open) static void ShowDummyObject(const char* prefix, int uid) { ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. - ImGui::AlignFirstTextHeightToWidgets(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); ImGui::NextColumn(); - ImGui::AlignFirstTextHeightToWidgets(); + ImGui::AlignTextToFramePadding(); ImGui::Text("my sailor is rich"); ImGui::NextColumn(); if (node_open) @@ -2714,7 +2775,7 @@ static void ShowExampleAppPropertyEditor(bool* p_open) } else { - ImGui::AlignFirstTextHeightToWidgets(); + ImGui::AlignTextToFramePadding(); // Here we use a Selectable (instead of Text) to highlight on hover //ImGui::Text("Field_%d", i); char label[32]; diff --git a/imgui/imgui_draw.cpp b/imgui/imgui_draw.cpp index d01aa52d..983cce14 100644 --- a/imgui/imgui_draw.cpp +++ b/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.51 +// dear imgui, v1.52 // (drawing and font code) // Contains implementation for @@ -21,10 +21,10 @@ #if !defined(alloca) #ifdef _WIN32 #include // alloca -#elif (defined(__FreeBSD__) || defined(FreeBSD_kernel) || defined(__DragonFly__)) && !defined(__GLIBC__) -#include // alloca. FreeBSD uses stdlib.h unless GLIBC -#else +#elif defined(__GLIBC__) || defined(__sun) #include // alloca +#else +#include // alloca #endif #endif @@ -42,6 +42,9 @@ #if __has_warning("-Wreserved-id-macro") #pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // #endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function @@ -69,7 +72,6 @@ namespace IMGUI_STB_NAMESPACE #ifdef __clang__ #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wunused-function" #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif @@ -220,7 +222,7 @@ void ImDrawList::UpdateTextureID() // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; - if (prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) CmdBuffer.pop_back(); else curr_cmd->TextureId = curr_texture_id; @@ -337,7 +339,7 @@ void ImDrawList::ChannelsMerge() if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } } - AddDrawCmd(); + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. _ChannelsCount = 1; } @@ -694,30 +696,30 @@ void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_ circle_vtx_builds = true; } - if (a_min_of_12 > a_max_of_12) return; - if (radius == 0.0f) + if (radius == 0.0f || a_min_of_12 > a_max_of_12) { _Path.push_back(centre); + return; } - else + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) { - _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); - for (int a = a_min_of_12; a <= a_max_of_12; a++) - { - const ImVec2& c = circle_vtx[a % circle_vtx_count]; - _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); - } + const ImVec2& c = circle_vtx[a % circle_vtx_count]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); } } -void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float amin, float amax, int num_segments) +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) { if (radius == 0.0f) + { _Path.push_back(centre); + return; + } _Path.reserve(_Path.Size + (num_segments + 1)); for (int i = 0; i <= num_segments; i++) { - const float a = amin + ((float)i / (float)num_segments) * (amax - amin); + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); _Path.push_back(ImVec2(centre.x + cosf(a) * radius, centre.y + sinf(a) * radius)); } } @@ -775,31 +777,30 @@ void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImV void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) { const int corners_top = ImGuiCorner_TopLeft | ImGuiCorner_TopRight; - const int corners_bottom = ImGuiCorner_BottomLeft | ImGuiCorner_BottomRight; - const int corners_left = ImGuiCorner_TopLeft | ImGuiCorner_BottomLeft; - const int corners_right = ImGuiCorner_TopRight | ImGuiCorner_BottomRight; + const int corners_bottom = ImGuiCorner_BotLeft | ImGuiCorner_BotRight; + const int corners_left = ImGuiCorner_TopLeft | ImGuiCorner_BotLeft; + const int corners_right = ImGuiCorner_TopRight | ImGuiCorner_BotRight; - float r = rounding; - r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners & corners_top) == corners_top) || ((rounding_corners & corners_bottom) == corners_bottom) ? 0.5f : 1.0f ) - 1.0f); - r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners & corners_left) == corners_left) || ((rounding_corners & corners_right) == corners_right) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.x - a.x) * ( ((rounding_corners & corners_top) == corners_top) || ((rounding_corners & corners_bottom) == corners_bottom) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.y - a.y) * ( ((rounding_corners & corners_left) == corners_left) || ((rounding_corners & corners_right) == corners_right) ? 0.5f : 1.0f ) - 1.0f); - if (r <= 0.0f || rounding_corners == 0) + if (rounding <= 0.0f || rounding_corners == 0) { PathLineTo(a); - PathLineTo(ImVec2(b.x,a.y)); + PathLineTo(ImVec2(b.x, a.y)); PathLineTo(b); - PathLineTo(ImVec2(a.x,b.y)); + PathLineTo(ImVec2(a.x, b.y)); } else { - const float r0 = (rounding_corners & ImGuiCorner_TopLeft) ? r : 0.0f; - const float r1 = (rounding_corners & ImGuiCorner_TopRight) ? r : 0.0f; - const float r2 = (rounding_corners & ImGuiCorner_BottomRight) ? r : 0.0f; - const float r3 = (rounding_corners & ImGuiCorner_BottomLeft) ? r : 0.0f; - PathArcToFast(ImVec2(a.x+r0,a.y+r0), r0, 6, 9); - PathArcToFast(ImVec2(b.x-r1,a.y+r1), r1, 9, 12); - PathArcToFast(ImVec2(b.x-r2,b.y-r2), r2, 0, 3); - PathArcToFast(ImVec2(a.x+r3,b.y-r3), r3, 3, 6); + const float rounding_tl = (rounding_corners & ImGuiCorner_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImGuiCorner_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImGuiCorner_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImGuiCorner_BotLeft) ? 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); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); } } @@ -1033,6 +1034,43 @@ void ImDrawData::ScaleClipRects(const ImVec2& scale) } } +//----------------------------------------------------------------------------- +// Shade functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Scan and shade backward from the end of given vertices. Assume vertices are text only (= vert_start..vert_end going left to right) so we can break as soon as we are out the gradient bounds. +void ImGui::ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x) +{ + float gradient_extent_x = gradient_p1_x - gradient_p0_x; + float gradient_inv_length2 = 1.0f / (gradient_extent_x * gradient_extent_x); + int full_alpha_count = 0; + for (ImDrawVert* vert = vert_end - 1; vert >= vert_start; vert--) + { + float d = (vert->pos.x - gradient_p0_x) * (gradient_extent_x); + float alpha_mul = 1.0f - ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + if (alpha_mul >= 1.0f && ++full_alpha_count > 2) + return; // Early out + int a = (int)(((vert->col >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul); + vert->col = (vert->col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); + } +} + //----------------------------------------------------------------------------- // ImFontConfig //----------------------------------------------------------------------------- @@ -1051,8 +1089,10 @@ ImFontConfig::ImFontConfig() GlyphOffset = ImVec2(0.0f, 0.0f); GlyphRanges = NULL; MergeMode = false; - DstFont = NULL; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; memset(Name, 0, sizeof(Name)); + DstFont = NULL; } //----------------------------------------------------------------------------- @@ -1063,7 +1103,7 @@ ImFontConfig::ImFontConfig() // The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 90; const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; -const int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0xF0000; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = { "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" @@ -1098,11 +1138,14 @@ const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF ImFontAtlas::ImFontAtlas() { TexID = NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; TexPixelsAlpha8 = NULL; TexPixelsRGBA32 = NULL; - TexWidth = TexHeight = TexDesiredWidth = 0; - TexGlyphPadding = 1; + TexWidth = TexHeight = 0; TexUvWhitePixel = ImVec2(0, 0); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; } ImFontAtlas::~ImFontAtlas() @@ -1119,7 +1162,7 @@ void ImFontAtlas::ClearInputData() ConfigData[i].FontData = NULL; } - // When clearing this we lose access to the font name and other information used to build the font. + // When clearing this we lose access to the font name and other information used to build the font. for (int i = 0; i < Fonts.Size; i++) if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) { @@ -1128,6 +1171,8 @@ void ImFontAtlas::ClearInputData() } ConfigData.clear(); CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; } void ImFontAtlas::ClearTexData() @@ -1280,7 +1325,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); } -// NBM Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); @@ -1315,8 +1360,9 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } -int ImFontAtlas::CustomRectRegister(unsigned int id, int width, int height) +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) { + IM_ASSERT(id >= 0x10000); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); CustomRect r; @@ -1327,7 +1373,23 @@ int ImFontAtlas::CustomRectRegister(unsigned int id, int width, int height) return CustomRects.Size - 1; // Return index } -void ImFontAtlas::CustomRectCalcUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) { IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed @@ -1340,6 +1402,23 @@ bool ImFontAtlas::Build() return ImFontAtlasBuildWithStbTruetype(this); } +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) { IM_ASSERT(atlas->ConfigData.Size > 0); @@ -1382,6 +1461,7 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) { stbtt_fontinfo FontInfo; stbrp_rect* Rects; + int RectsCount; stbtt_pack_range* Ranges; int RangesCount; }; @@ -1395,7 +1475,10 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); IM_ASSERT(font_offset >= 0); if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + ImGui::MemFree(tmp_array); return false; + } } // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) @@ -1434,6 +1517,7 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) // Pack tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; buf_rects_n += font_glyphs_count; stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); @@ -1463,6 +1547,14 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) ImFontTempBuildData& tmp = tmp_array[input_i]; stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } tmp.Rects = NULL; } @@ -1478,17 +1570,16 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) ImFontTempBuildData& tmp = tmp_array[input_i]; ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) - float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); int unscaled_ascent, unscaled_descent, unscaled_line_gap; stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - float ascent = unscaled_ascent * font_scale; - float descent = unscaled_descent * font_scale; + const float ascent = unscaled_ascent * font_scale; + const float descent = unscaled_descent * font_scale; ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); - float off_x = cfg.GlyphOffset.x; - float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + const float off_x = cfg.GlyphOffset.x; + const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); - dst_font->FallbackGlyph = NULL; // Always clear fallback so FindGlyph can return NULL. It will be set again in BuildLookupTable() for (int i = 0; i < tmp.RangesCount; i++) { stbtt_pack_range& range = tmp.Ranges[i]; @@ -1505,26 +1596,9 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) stbtt_aligned_quad q; float dummy_x = 0.0f, dummy_y = 0.0f; stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); - - dst_font->Glyphs.resize(dst_font->Glyphs.Size + 1); - ImFont::Glyph& glyph = dst_font->Glyphs.back(); - glyph.Codepoint = (ImWchar)codepoint; - glyph.X0 = q.x0 + off_x; - glyph.Y0 = q.y0 + off_y; - glyph.X1 = q.x1 + off_x; - glyph.Y1 = q.y1 + off_y; - glyph.U0 = q.s0; - glyph.V0 = q.t0; - glyph.U1 = q.s1; - glyph.V1 = q.t1; - glyph.XAdvance = (pc.xadvance + cfg.GlyphExtraSpacing.x); // Bake spacing into XAdvance - - if (cfg.PixelSnapH) - glyph.XAdvance = (float)(int)(glyph.XAdvance + 0.5f); - dst_font->MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * atlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * atlas->TexHeight + 1.99f); // +1 to account for average padding, +0.99 to round + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + off_x, q.y0 + off_y, q.x1 + off_x, q.y1 + off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); } } - cfg.DstFont->BuildLookupTable(); } // Cleanup temporaries @@ -1532,31 +1606,27 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) ImGui::MemFree(buf_ranges); ImGui::MemFree(tmp_array); - // Render into our custom data block - ImFontAtlasBuildRenderDefaultTexData(atlas); + ImFontAtlasBuildFinish(atlas); return true; } void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) { - // FIXME-WIP: We should register in the constructor (but cannot because our static instances may not have allocator ready by the time they initialize). This needs to be fixed because we can expose CustomRects. - if (atlas->CustomRects.empty()) - atlas->CustomRectRegister(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + if (atlas->CustomRectIds[0] < 0) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); } void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) { if (!font_config->MergeMode) { - font->ContainerAtlas = atlas; - font->ConfigData = font_config; - font->ConfigDataCount = 0; + font->ClearOutputData(); font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; font->Ascent = ascent; font->Descent = descent; - font->Glyphs.resize(0); - font->MetricsTotalSurface = 0; } font->ConfigDataCount++; } @@ -1566,6 +1636,8 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaq stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + ImVector pack_rects; pack_rects.resize(user_rects.Size); memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); @@ -1585,9 +1657,10 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaq } } -void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) { - ImFontAtlas::CustomRect& r = atlas->CustomRects[0]; + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1); IM_ASSERT(r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); @@ -1635,6 +1708,29 @@ void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) } } +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + atlas->Fonts[i]->BuildLookupTable(); +} + // Retrieve list of range (2 int per range, values are inclusive) const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { @@ -1675,7 +1771,10 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChinese() const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() { // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. - // This encoding helps us reduce the source code size. + // This encoding is designed to helps us reduce the source code size. + // FIXME: Source a list of the revised 2136 joyo kanji list from 2010 and rebuild this. + // The current list was sourced from http://theinstructionlimit.com/author/renaudbedardrenaudbedard/page/3 + // Note that you may use ImFontAtlas::GlyphRangesBuilder to create your own ranges, by merging existing ranges or adding new characters. static const short offsets_from_0x4E00[] = { -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, @@ -1804,7 +1903,8 @@ ImFont::ImFont() { Scale = 1.0f; FallbackChar = (ImWchar)'?'; - Clear(); + DisplayOffset = ImVec2(0.0f, 1.0f); + ClearOutputData(); } ImFont::~ImFont() @@ -1817,18 +1917,17 @@ ImFont::~ImFont() if (g.Font == this) g.Font = NULL; */ - Clear(); + ClearOutputData(); } -void ImFont::Clear() +void ImFont::ClearOutputData() { FontSize = 0.0f; - DisplayOffset = ImVec2(0.0f, 1.0f); Glyphs.clear(); - IndexXAdvance.clear(); + IndexAdvanceX.clear(); IndexLookup.clear(); FallbackGlyph = NULL; - FallbackXAdvance = 0.0f; + FallbackAdvanceX = 0.0f; ConfigDataCount = 0; ConfigData = NULL; ContainerAtlas = NULL; @@ -1843,13 +1942,13 @@ void ImFont::BuildLookupTable() max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved - IndexXAdvance.clear(); + IndexAdvanceX.clear(); IndexLookup.clear(); GrowIndex(max_codepoint + 1); for (int i = 0; i < Glyphs.Size; i++) { int codepoint = (int)Glyphs[i].Codepoint; - IndexXAdvance[codepoint] = Glyphs[i].XAdvance; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; IndexLookup[codepoint] = (unsigned short)i; } @@ -1859,20 +1958,20 @@ void ImFont::BuildLookupTable() { if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times Glyphs.resize(Glyphs.Size + 1); - ImFont::Glyph& tab_glyph = Glyphs.back(); + ImFontGlyph& tab_glyph = Glyphs.back(); tab_glyph = *FindGlyph((unsigned short)' '); tab_glyph.Codepoint = '\t'; - tab_glyph.XAdvance *= 4; - IndexXAdvance[(int)tab_glyph.Codepoint] = (float)tab_glyph.XAdvance; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); } FallbackGlyph = NULL; FallbackGlyph = FindGlyph(FallbackChar); - FallbackXAdvance = FallbackGlyph ? FallbackGlyph->XAdvance : 0.0f; + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; for (int i = 0; i < max_codepoint + 1; i++) - if (IndexXAdvance[i] < 0.0f) - IndexXAdvance[i] = FallbackXAdvance; + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; } void ImFont::SetFallbackChar(ImWchar c) @@ -1883,17 +1982,33 @@ void ImFont::SetFallbackChar(ImWchar c) void ImFont::GrowIndex(int new_size) { - IM_ASSERT(IndexXAdvance.Size == IndexLookup.Size); - int old_size = IndexLookup.Size; - if (new_size <= old_size) + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) return; - IndexXAdvance.resize(new_size); - IndexLookup.resize(new_size); - for (int i = old_size; i < new_size; i++) - { - IndexXAdvance[i] = -1.0f; - IndexLookup[i] = (unsigned short)-1; - } + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (unsigned short)-1); +} + +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); } void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) @@ -1908,10 +2023,10 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) GrowIndex(dst + 1); IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; - IndexXAdvance[dst] = (src < index_size) ? IndexXAdvance.Data[src] : 1.0f; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; } -const ImFont::Glyph* ImFont::FindGlyph(unsigned short c) const +const ImFontGlyph* ImFont::FindGlyph(unsigned short c) const { if (c < IndexLookup.Size) { @@ -1976,7 +2091,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } } - const float char_width = ((int)c < IndexXAdvance.Size ? IndexXAdvance[(int)c] : FallbackXAdvance); + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); if (ImCharIsSpace(c)) { if (inside_word) @@ -2093,7 +2208,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ((int)c < IndexXAdvance.Size ? IndexXAdvance[(int)c] : FallbackXAdvance) * scale; + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; if (line_width + char_width >= max_width) { s = prev_s; @@ -2119,7 +2234,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. return; - if (const Glyph* glyph = FindGlyph(c)) + if (const ImFontGlyph* glyph = FindGlyph(c)) { float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; pos.x = (float)(int)pos.x + DisplayOffset.x; @@ -2223,9 +2338,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col } float char_width = 0.0f; - if (const Glyph* glyph = FindGlyph((unsigned short)c)) + if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) { - char_width = glyph->XAdvance * scale; + char_width = glyph->AdvanceX * scale; // Arbitrarily assume that both space and tabs are empty glyphs as an optimization if (c != ' ' && c != '\t') @@ -2301,6 +2416,78 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; } +//----------------------------------------------------------------------------- +// Internals Drawing Helpers +//----------------------------------------------------------------------------- + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return acosf(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + //----------------------------------------------------------------------------- // DEFAULT FONT DATA //----------------------------------------------------------------------------- diff --git a/imgui/imgui_internal.h b/imgui/imgui_internal.h index cee3f733..b5869775 100644 --- a/imgui/imgui_internal.h +++ b/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.51 +// dear imgui, v1.52 // (internals) // You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! @@ -44,10 +44,11 @@ struct ImGuiMouseCursorData; struct ImGuiPopupRef; struct ImGuiWindow; -typedef int ImGuiLayoutType; // enum ImGuiLayoutType_ -typedef int ImGuiButtonFlags; // enum ImGuiButtonFlags_ -typedef int ImGuiTreeNodeFlags; // enum ImGuiTreeNodeFlags_ -typedef int ImGuiSliderFlags; // enum ImGuiSliderFlags_ +typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ +typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ +typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ //------------------------------------------------------------------------- // STB libraries @@ -77,9 +78,8 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointe // Helpers //----------------------------------------------------------------------------- -#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) -#define IM_PI 3.14159265358979323846f -#define IM_OFFSETOF(_TYPE,_ELM) ((size_t)&(((_TYPE*)0)->_ELM)) +#define IM_PI 3.14159265358979323846f +#define IM_OFFSETOF(_TYPE,_ELM) ((size_t)&(((_TYPE*)0)->_ELM)) // Helpers: UTF-8 <> wchar IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count @@ -105,12 +105,13 @@ IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec // Helpers: String IMGUI_API int ImStricmp(const char* str1, const char* str2); IMGUI_API int ImStrnicmp(const char* str1, const char* str2, int count); +IMGUI_API void ImStrncpy(char* dst, const char* src, int count); IMGUI_API char* ImStrdup(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); -IMGUI_API int ImFormatString(char* buf, int buf_size, const char* fmt, ...) IM_PRINTFARGS(3); -IMGUI_API int ImFormatStringV(char* buf, int buf_size, const char* fmt, va_list args); +IMGUI_API int ImFormatString(char* buf, int buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, int buf_size, const char* fmt, va_list args) IM_FMTLIST(3); // Helpers: Math // We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) @@ -125,7 +126,9 @@ static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } #endif static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } @@ -138,10 +141,13 @@ static inline int ImClamp(int v, int mn, int mx) static inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } static inline ImVec2 ImClamp(const ImVec2& f, const ImVec2& mn, ImVec2 mx) { return ImVec2(ImClamp(f.x,mn.x,mx.x), ImClamp(f.y,mn.y,mx.y)); } static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline void ImSwap(int& a, int& b) { int tmp = a; a = b; b = tmp; } +static inline void ImSwap(float& a, float& b) { float tmp = a; a = b; b = tmp; } static inline int ImLerp(int a, int b, float t) { return (int)(a + (b - a) * t); } static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / sqrtf(d); return fail_value; } @@ -149,6 +155,7 @@ static inline float ImFloor(float f) static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } // We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. // Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. @@ -166,16 +173,17 @@ inline void operator delete(void*, ImPlacementNewDummy, void*) {} enum ImGuiButtonFlags_ { ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat - ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // (default) return pressed on click+release on same item (default if no PressedOn** flag is set) - ImGuiButtonFlags_PressedOnClick = 1 << 2, // return pressed on click (default requires click+release) - ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return pressed on release (default requires click+release) - ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return pressed on double-click (default requires click+release) - ImGuiButtonFlags_FlattenChilds = 1 << 5, // allow interaction even if a child window is overlapping - ImGuiButtonFlags_DontClosePopups = 1 << 6, // disable automatically closing parent popup on press - ImGuiButtonFlags_Disabled = 1 << 7, // disable interaction - ImGuiButtonFlags_AlignTextBaseLine = 1 << 8, // vertically align button to match text baseline - ButtonEx() only + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChilds = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_DontClosePopups = 1 << 6, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 7, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 8, // vertically align button to match text baseline (ButtonEx() only) ImGuiButtonFlags_NoKeyModifiers = 1 << 9, // disable interaction if a key modifier is held - ImGuiButtonFlags_AllowOverlapMode = 1 << 10 // require previous frame HoveredId to either match id or be null before being usable + ImGuiButtonFlags_AllowOverlapMode = 1 << 10, // require previous frame HoveredId to either match id or be null before being usable + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11 // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) }; enum ImGuiSliderFlags_ @@ -201,6 +209,12 @@ enum ImGuiSelectableFlagsPrivate_ ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 6 }; +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + // FIXME: this is in development, not exposed/functional as a generic feature yet. enum ImGuiLayoutType_ { @@ -218,7 +232,7 @@ enum ImGuiDataType { ImGuiDataType_Int, ImGuiDataType_Float, - ImGuiDataType_Float2, + ImGuiDataType_Float2 }; enum ImGuiDir @@ -227,15 +241,15 @@ enum ImGuiDir ImGuiDir_Left = 0, ImGuiDir_Right = 1, ImGuiDir_Up = 2, - ImGuiDir_Down = 3, + ImGuiDir_Down = 3 }; enum ImGuiCorner { ImGuiCorner_TopLeft = 1 << 0, // 1 ImGuiCorner_TopRight = 1 << 1, // 2 - ImGuiCorner_BottomRight = 1 << 2, // 4 - ImGuiCorner_BottomLeft = 1 << 3, // 8 + ImGuiCorner_BotRight = 1 << 2, // 4 + ImGuiCorner_BotLeft = 1 << 3, // 8 ImGuiCorner_All = 0x0F }; @@ -420,11 +434,10 @@ struct ImGuiContext bool ActiveIdIsAlive; // Active widget has been seen this frame bool ActiveIdIsJustActivated; // Set at the time of activation for one frame bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) - ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; - ImGuiWindow* MovedWindow; // Track the child window we clicked on to move a window. - ImGuiID MovedWindowMoveId; // == MovedWindow->RootWindow->MoveId + ImGuiWindow* MovingWindow; // Track the child window we clicked on to move a window. + ImGuiID MovingWindowMoveId; // == MovingWindow->MoveId ImVector Settings; // .ini Settings float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() @@ -435,6 +448,7 @@ struct ImGuiContext // Storage for SetNexWindow** and SetNextTreeNode*** functions ImVec2 SetNextWindowPosVal; + ImVec2 SetNextWindowPosPivot; ImVec2 SetNextWindowSizeVal; ImVec2 SetNextWindowContentSizeVal; bool SetNextWindowCollapsedVal; @@ -485,8 +499,9 @@ struct ImGuiContext float FramerateSecPerFrame[120]; // calculate estimate of framerate for user int FramerateSecPerFrameIdx; float FramerateSecPerFrameAccum; - int CaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags - int CaptureKeyboardNextFrame; + int WantCaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; char TempBuffer[1024*3+1]; // temporary text buffer ImGuiContext() @@ -513,8 +528,8 @@ struct ImGuiContext ActiveIdAllowOverlap = false; ActiveIdClickOffset = ImVec2(-1,-1); ActiveIdWindow = NULL; - MovedWindow = NULL; - MovedWindowMoveId = 0; + MovingWindow = NULL; + MovingWindowMoveId = 0; SettingsDirtyTimer = 0.0f; SetNextWindowPosVal = ImVec2(0.0f, 0.0f); @@ -537,7 +552,7 @@ struct ImGuiContext DragCurrentValue = 0.0f; DragLastMouseDelta = ImVec2(0.0f, 0.0f); DragSpeedDefaultRatio = 1.0f / 100.0f; - DragSpeedScaleSlow = 0.01f; + DragSpeedScaleSlow = 1.0f / 100.0f; DragSpeedScaleFast = 10.0f; ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); TooltipOverrideCount = 0; @@ -557,11 +572,23 @@ struct ImGuiContext memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); FramerateSecPerFrameIdx = 0; FramerateSecPerFrameAccum = 0.0f; - CaptureMouseNextFrame = CaptureKeyboardNextFrame = -1; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; memset(TempBuffer, 0, sizeof(TempBuffer)); } }; +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP) + //ImGuiItemFlags_NoNav = 1 << 3, // false + //ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + // Transient per-window data, reset at the beginning of the frame // FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiDrawContext is quite tenuous and could be reconsidered. struct IMGUI_API ImGuiDrawContext @@ -578,8 +605,7 @@ struct IMGUI_API ImGuiDrawContext int TreeDepth; ImGuiID LastItemId; ImRect LastItemRect; - bool LastItemHoveredAndUsable; // Item rectangle is hovered, and its window is currently interactable with (not blocked by a popup preventing access to the window) - bool LastItemHoveredRect; // Item rectangle is hovered, but its window may or not be currently interactable with (might be blocked by a popup preventing access to the window) + bool LastItemRectHoveredRect; bool MenuBarAppending; float MenuBarOffsetX; ImVector ChildWindows; @@ -587,14 +613,12 @@ struct IMGUI_API ImGuiDrawContext ImGuiLayoutType LayoutType; // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] - bool AllowKeyboardFocus; // == AllowKeyboardFocusStack.back() [empty == true] - bool ButtonRepeat; // == ButtonRepeatStack.back() [empty == false] + ImVectorItemFlagsStack; ImVector ItemWidthStack; ImVector TextWrapPosStack; - ImVector AllowKeyboardFocusStack; - ImVector ButtonRepeatStack; ImVectorGroupStack; int StackSizesBackup[6]; // Store size of various stacks for asserting @@ -621,15 +645,14 @@ struct IMGUI_API ImGuiDrawContext LogLinePosY = -1.0f; TreeDepth = 0; LastItemId = 0; - LastItemRect = ImRect(0.0f,0.0f,0.0f,0.0f); - LastItemHoveredAndUsable = LastItemHoveredRect = false; + LastItemRect = ImRect(); + LastItemRectHoveredRect = false; MenuBarAppending = false; MenuBarOffsetX = 0.0f; StateStorage = NULL; LayoutType = ImGuiLayoutType_Vertical; ItemWidth = 0.0f; - ButtonRepeat = false; - AllowKeyboardFocus = true; + ItemFlags = ImGuiItemFlags_Default_; TextWrapPos = -1.0f; memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); @@ -673,7 +696,8 @@ struct IMGUI_API ImGuiWindow bool WasActive; bool Accessed; // Set to true when any widget access the current window bool Collapsed; // Set when collapsing window to become only title-bar - bool SkipItems; // == Visible && !Collapsed + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) int AutoFitFramesX, AutoFitFramesY; @@ -684,21 +708,23 @@ struct IMGUI_API ImGuiWindow ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. ImGuiCond SetWindowCollapsedAllowFlags; // store condition flags for next SetWindowCollapsed() call. - bool SetWindowPosCenterWanted; + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; int LastFrameActive; float ItemWidthDefault; ImGuiSimpleColumns MenuColumns; // Simplified columns storage for menu items ImGuiStorage StateStorage; float FontWindowScale; // Scale multiplier per-window ImDrawList* DrawList; - ImGuiWindow* RootWindow; // If we are a child window, this is pointing to the first non-child parent window. Else point to ourself. - ImGuiWindow* RootNonPopupWindow; // If we are a child window, this is pointing to the first non-child non-popup parent window. Else point to ourself. - ImGuiWindow* ParentWindow; // If we are a child window, this is pointing to our parent window. Else point to NULL. + ImGuiWindow* ParentWindow; // Immediate parent in the window stack *regardless* of whether this window is a child window or not) + ImGuiWindow* RootWindow; // Generally point to ourself. If we are a child window, this is pointing to the first non-child parent window. + ImGuiWindow* RootNonPopupWindow; // Generally point to ourself. Used to display TitleBgActive color and for selecting which window to use for NavWindowing // Navigation / Focus int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() @@ -724,6 +750,17 @@ public: ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } }; +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImRect LastItemRect; + bool LastItemRectHoveredRect; + + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemRect = window->DC.LastItemRect; LastItemRectHoveredRect = window->DC.LastItemRectHoveredRect; } + void Restore() { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemRect = LastItemRect; window->DC.LastItemRectHoveredRect = LastItemRectHoveredRect; } +}; + //----------------------------------------------------------------------------- // Internal API // No guarantee of forward compatibility here. @@ -741,6 +778,7 @@ namespace ImGui IMGUI_API ImGuiWindow* FindWindowByName(const char* name); IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void Initialize(); IMGUI_API void EndFrame(); // Ends the ImGui frame. Automatically called by Render()! you most likely don't need to ever call that yourself directly. If you don't need to render you can call EndFrame() but you'll have wasted CPU already. If you don't need to render, don't create any windows instead! IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); @@ -750,22 +788,37 @@ namespace ImGui IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); - IMGUI_API bool ItemAdd(const ImRect& bb, const ImGuiID* id); - IMGUI_API bool IsClippedEx(const ImRect& bb, const ImGuiID* id, bool clip_even_when_logged); - IMGUI_API bool IsHovered(const ImRect& bb, ImGuiID id, bool flatten_childs = false); - IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, bool is_active, bool tab_stop = true); // Return true if focus is requested + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); IMGUI_API void OpenPopupEx(ImGuiID id, bool reopen_existing); + IMGUI_API void ClosePopup(ImGuiID id); IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); - // New Columns API + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + + // FIXME-WIP: New Columns API IMGUI_API void BeginColumns(const char* id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). IMGUI_API void EndColumns(); // close columns IMGUI_API void PushColumnClipRect(int column_index = -1); + // FIXME-WIP: New Combo API + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImVec2 popup_size = ImVec2(0.0f,0.0f)); + IMGUI_API void EndCombo(); + // NB: All position are in absolute pixels coordinates (never using window coordinates internally) // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); @@ -774,9 +827,10 @@ namespace ImGui IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); - IMGUI_API void RenderCollapseTriangle(ImVec2 pos, bool is_open, float scale = 1.0f); + IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); IMGUI_API void RenderBullet(ImVec2 pos); - IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); @@ -797,7 +851,8 @@ namespace ImGui IMGUI_API bool InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags); IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision); - IMGUI_API void ColorTooltip(const char* text, const float col[4], ImGuiColorEditFlags flags); + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging @@ -808,6 +863,10 @@ namespace ImGui IMGUI_API int ParseFormatPrecision(const char* fmt, int default_value); IMGUI_API float RoundScalar(float value, int decimal_precision); + // Shade functions + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x); + } // namespace ImGui // ImFontAtlas internals @@ -815,7 +874,9 @@ IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); -IMGUI_API void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); #ifdef __clang__ #pragma clang diagnostic pop