Compare commits

..

1 Commits

Author SHA1 Message Date
Ben Carter
0af8ae979d Shadows: Added experimental texture-based shadows (stripped of dynamic tex config and back-end code)
(merged 10 commits, removing dynamic tex config, moved tex config to internal structs, removed back-end changes)
(squashed 2025-06-11, fixed conflict, doesn't build as-is)
Shadows: Added IMGUI_HAS_SHADOWS
Shadows: Demo code in Custom Rendering section. Added AddShadowRectFilled() variant. BeginMainMenuBar() disable shadows.
Shadows: Added initial version of convex shape shadow code.
(+stripped out of original polygon generation demo, moved to imgui_dev)
Shadows: Tweak demo to use AddShadowCircle() functions + fix warnings.
 (+ stripped old polygon generation code from commits)
Shadows: Convex shape shadow improvement/fixes
- Fixed pixel cracking on convex shadow edges
- Added convex shadow offset support
- Fixed some convex shadow fringing issues
- Added convex shadow demo code
Shadows: Added ImDrawShadowFlags, simplified API surface, reordered parameters
+ fix minor warnings
+ removed NGon variant for now.
Shadows: Re-added AddShadowNGon().
Shadows: Shallow styling tweaks and renaming for consistency.
Shadows: Fixes + two fixes for PVS Studio static analyzer.
Shadows: Fix for latest, reuse ImDrawList, remove ImDrawShadowFlags.
Shadows: Fix to support for colored-only font atlas. (4202)
Shadows: Fix broken shadows due to zero-clear added in 9417acc2.
Shadows: remove use of GetWindowContentRegionMax().
Shadows: WIP fixes for latest. (broken UV caching)
Shadows: WIP for latest (refreshing UV coordinates).
Demo: Add missing trailing null terminator to convex shape combo (9434)
2026-06-08 12:13:28 +02:00
6 changed files with 1271 additions and 348 deletions

View File

@@ -1421,6 +1421,7 @@ static void UpdateTexturesNewFrame();
static void UpdateTexturesEndFrame();
static void UpdateSettings();
static int UpdateWindowManualResize(ImGuiWindow* window, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect);
static void RenderWindowShadow(ImGuiWindow* window);
static void RenderWindowOuterBorders(ImGuiWindow* window);
static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size);
static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open);
@@ -1549,6 +1550,9 @@ ImGuiStyle::ImGuiStyle()
AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
WindowShadowSize = 100.0f; // Size (in pixels) of window shadows.
WindowShadowOffsetDist = 0.0f; // Offset distance (in pixels) of window shadows from casting window.
WindowShadowOffsetAngle = IM_PI * 0.25f; // Offset angle of window shadows from casting window (0.0f = left, 0.5f*PI = bottom, 1.0f*PI = right, 1.5f*PI = top).
// Behaviors
HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary.
@@ -3823,6 +3827,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx)
case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg";
case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg";
case ImGuiCol_WindowShadow: return "WindowShadow";
}
IM_ASSERT(0);
return "Unknown";
@@ -7232,6 +7237,11 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
}
}
// Draw window shadow
if (style.WindowShadowSize > 0.0f && (!(flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_Popup)))
if (style.Colors[ImGuiCol_WindowShadow].w > 0.0f)
RenderWindowShadow(window);
// Title bar
if (!(flags & ImGuiWindowFlags_NoTitleBar))
{
@@ -7280,6 +7290,16 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
}
void ImGui::RenderWindowShadow(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
ImGuiStyle& style = g.Style;
float shadow_size = style.WindowShadowSize;
ImU32 shadow_col = GetColorU32(ImGuiCol_WindowShadow);
ImVec2 shadow_offset = ImVec2(ImCos(style.WindowShadowOffsetAngle), ImSin(style.WindowShadowOffsetAngle)) * style.WindowShadowOffsetDist;
window->DrawList->AddShadowRect(window->Pos, window->Pos + window->Size, shadow_col, shadow_size, shadow_offset, ImDrawFlags_ShadowCutOutShapeBackground, window->WindowRounding);
}
// Render title text, collapse button, close button
void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open)
{
@@ -9160,6 +9180,12 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling)
g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL;
g.FontBakedScale = (g.FontBaked != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f;
g.DrawListSharedData.FontScale = g.FontBakedScale;
if (g.Font)
{
ImFontAtlas* atlas = g.Font->OwnerAtlas;
g.DrawListSharedData.ShadowRectIds = &atlas->ShadowRectIds[0];
g.DrawListSharedData.ShadowRectUvs = &atlas->ShadowRectUvs[0];
}
}
// Exposed in case user may want to override setting density.

47
imgui.h
View File

@@ -1798,6 +1798,7 @@ enum ImGuiCol_
ImGuiCol_NavWindowingHighlight, // Highlight window when using Ctrl+Tab
ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the Ctrl+Tab window list, when active
ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active
ImGuiCol_WindowShadow, // Window shadows
ImGuiCol_COUNT,
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
@@ -2351,6 +2352,10 @@ struct ImGuiStyle
float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
float WindowShadowSize; // Size (in pixels) of window shadows. Set this to zero to disable shadows.
float WindowShadowOffsetDist; // Offset distance (in pixels) of window shadows from casting window.
float WindowShadowOffsetAngle; // Offset angle of window shadows from casting window (0.0f = left, 0.5f*PI = bottom, 1.0f*PI = right, 1.5f*PI = top).
// Colors
ImVec4 Colors[ImGuiCol_COUNT];
@@ -3259,6 +3264,8 @@ enum ImDrawFlags_
ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified.
ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone,
ImDrawFlags_InvalidMask_ = (ImDrawFlags)0x8000000F,
ImDrawFlags_ShadowCutOutShapeBackground = 1 << 20, // Do not render the shadow shape under the objects to be shadowed to save on fill-rate or facilitate blending. Slower on CPU.
};
// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly.
@@ -3359,6 +3366,23 @@ struct ImDrawList
IMGUI_API void AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE);
IMGUI_API void AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0);
// Shadows primitives
// [BETA] API
// - Add shadow for a object, with min/max or center/radius describing the object extents, and offset shifting the shadow.
// - Rounding parameters refer to the object itself, not the shadow!
// - By default, the area under the object is filled, because this is simpler to process.
// Using the ImDrawFlags_ShadowCutOutShapeBackground flag makes the function not render this area and leave a hole under the object.
// - Shadows w/ fill under the object: a bit faster for CPU, more pixels rendered, visible/darkening if used behind a transparent shape.
// Typically used by: small, frequent objects, opaque objects, transparent objects if shadow darkening isn't an issue.
// - Shadows w/ hole under the object: a bit slower for CPU, less pixels rendered, no difference if used behind a transparent shape.
// Typically used by: large, infrequent objects, transparent objects if exact blending/color matter.
// - FIXME-SHADOWS: 'offset' + ImDrawFlags_ShadowCutOutShapeBackground are not currently supported together with AddShadowCircle(), AddShadowConvexPoly(), AddShadowNGon().
#define IMGUI_HAS_SHADOWS 1
IMGUI_API void AddShadowRect(const ImVec2& obj_min, const ImVec2& obj_max, ImU32 shadow_col, float shadow_thickness, const ImVec2& shadow_offset, ImDrawFlags flags = 0, float obj_rounding = 0.0f);
IMGUI_API void AddShadowCircle(const ImVec2& obj_center, float obj_radius, ImU32 shadow_col, float shadow_thickness, const ImVec2& shadow_offset, ImDrawFlags flags = 0, int obj_num_segments = 12);
IMGUI_API void AddShadowConvexPoly(const ImVec2* points, int points_count, ImU32 shadow_col, float shadow_thickness, const ImVec2& shadow_offset, ImDrawFlags flags = 0);
IMGUI_API void AddShadowNGon(const ImVec2& obj_center, float obj_radius, ImU32 shadow_col, float shadow_thickness, const ImVec2& shadow_offset, ImDrawFlags flags, int obj_num_segments);
// Stateful path API, add points then finish with PathFillConvex() or PathStroke()
// - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing.
// so e.g. 'PathArcTo(center, radius, PI * -0.5f, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5f)' won't have correct anti-aliasing when followed by PathFillConvex().
@@ -3558,6 +3582,24 @@ struct ImTextureData
// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont)
//-----------------------------------------------------------------------------
// [Internal] Shadow texture baking config
struct ImFontAtlasShadowTexConfig
{
int TexCornerSize; // Size of the corner areas.
int TexEdgeSize; // Size of the edge areas (and by extension the center). Changing this is normally unnecessary.
float TexFalloffPower; // The power factor for the shadow falloff curve.
float TexDistanceFieldOffset; // How much to offset the distance field by (allows over/under-shadowing, potentially useful for accommodating rounded corners on the "casting" shape).
bool TexBlur; // Do we want to Gaussian blur the shadow texture?
inline ImFontAtlasShadowTexConfig() { memset(this, 0, sizeof(*this)); }
IMGUI_API void SetupDefaults();
int GetRectTexPadding() const { return 2; } // Number of pixels of padding to add to the rectangular texture to avoid sampling artifacts at the edges.
int CalcRectTexSize() const { return TexCornerSize + TexEdgeSize + GetRectTexPadding(); } // The size of the texture area required for the actual 2x2 rectangle shadow texture (after the redundant corners have been removed). Padding is required here to avoid sampling artifacts at the edge adjoining the removed corners. int CalcConvexTexWidth() const; // The width of the texture area required for the convex shape shadow texture.
int GetConvexTexPadding() const { return 8; } // Number of pixels of padding to add to the convex shape texture to avoid sampling artifacts at the edges. This also acts as padding for the expanded corner triangles.
int CalcConvexTexWidth() const; // The width of the texture area required for the convex shape shadow texture.
int CalcConvexTexHeight() const; // The height of the texture area required for the convex shape shadow texture.
};
// A font input/source (we may rename this to ImFontSource in the future)
struct ImFontConfig
{
@@ -3809,6 +3851,11 @@ struct ImFontAtlas
int RefCount; // Number of contexts using this atlas
ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it.
// [Internal] Shadow data
ImFontAtlasRectId ShadowRectIds[2]; // IDs of rect for shadow textures
ImVec4 ShadowRectUvs[10]; // UV coordinates for shadow textures, 9 for the rectangle shadows and the final entry for the convex shape shadows
ImFontAtlasShadowTexConfig ShadowTexConfig; // Shadow texture baking config
// [Obsolete]
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
// Legacy: You can 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. --> Prefer using a custom ImFontLoader.

View File

@@ -7897,8 +7897,6 @@ static void DemoWindowColumns()
// [SECTION] DemoWindowInputs()
//-----------------------------------------------------------------------------
#include "imgui_internal.h" // FIXME Until the new key owner/routing system are in public API this section of the demo needs internal (and is kept in a branch).
static void DemoWindowInputs()
{
if (ImGui::CollapsingHeader("Inputs & Focus"))
@@ -8084,353 +8082,7 @@ static void DemoWindowInputs()
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::TreePop();
}
// Ownership, Routings
IMGUI_DEMO_MARKER("Inputs & Focus/Key Ownership");
if (ImGui::TreeNode("Key Ownership"))
{
HelpMarker("See 'Tools->Metrics/Debugger->Inputs' to visualize ownership/routing data.");
// Demonstrate basic key ownership system
// Standard widgets all claim and test for key ownership
// (note that the ActiveId and HoveredId systems also generally prevents multiple items from interacting, but at a different level)
if (ImGui::TreeNode("1. Standard widgets taking input ownership"))
{
HelpMarker("Standard widgets claim and test for key ownership.\n\n\"Keys\" include mouse buttons, gamepad axises etc.");
const ImGuiKey key = ImGuiKey_MouseLeft; // Note how mouse and gamepad are also included in ImGuiKey: same data type for all.
const char* key_name = ImGui::GetKeyName(key);
ImGui::Text("Press '%s'", key_name);
ImGui::Text("1st read: (button)");
ImGui::Button("Click and Hold Me Tight!");
// Assume this is another piece of code running later.
// The *default* value for owner is ImGuiKeyOwner_Any, same as calling the simplified function:
// IsKeyDown(key) == IsKeyDown(key, ImGuiKeyOwner_Any)
// IsKeyPressed(key) == IsKeyPressed(key, ImGuiKeyOwner_Any)
// But notice the "bool repeat = true" parameter in old signature 'IsKeyPressed(key, repeat)'
// with the new signature becomes 'IsKeyPressed(key, owner, ImGuiInputFlags_Repeat)'
ImGui::Text("2nd read: (NOT owner-aware)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key) ? "PRESSED!" : "..");
ImGui::Text("3rd read: (owner-aware: ImGuiKeyOwner_NoOwner)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key, ImGuiKeyOwner_NoOwner) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) ? "PRESSED!" : "..");
ImGuiID another_owner = ImGui::GetID("AnotherItem");
ImGui::Text("4nd read: (owner-aware: different owner)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key, another_owner) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, ImGuiInputFlags_Repeat, another_owner) ? "PRESSED!" : "..");
ImGui::TreePop();
}
if (ImGui::TreeNode("2. Calling SetKeyOwner()"))
{
const ImGuiKey key = ImGuiKey_A;
const char* key_name = ImGui::GetKeyName(key);
ImGui::Text("Press '%s'", key_name);
ImGui::Text("1st read:");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, false) ? "PRESSED!" : "..");
ImGui::Text("...when pressed, call SetKeyOwner() with an owner ID.");
ImGuiID owner_1 = ImGui::GetID("MyItemID");
if (ImGui::IsKeyPressed(key, ImGuiInputFlags_Repeat, owner_1))
ImGui::SetKeyOwner(key, owner_1);
// Assume this is another piece of code running later.
// (same comments as in section 1)
ImGui::Text("2nd read: (NOT owner-aware)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key) ? "PRESSED!" : "..");
ImGui::Text("3rd read: (owner-aware: ImGuiKeyOwner_NoOwner)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key, ImGuiKeyOwner_NoOwner) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) ? "PRESSED!" : "..");
ImGuiID another_owner = ImGui::GetID("AnotherItem");
ImGui::Text("4th read: (owner-aware: different owner)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key, another_owner) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, ImGuiInputFlags_Repeat, another_owner) ? "PRESSED!" : "..");
ImGui::TreePop();
}
// Demonstrate using SetKeyOwner() with ImGuiInputFlags_LockThisFrame / ImGuiInputFlags_LockUntilRelease flags.
// - Using an owner id solves all/most cases as long as everyone is "owner-id-aware",
// meaning they call the long form of IsKeyXXX function. This is the preferred way to do things.
// - Using ImGuiInputFlags_LockXXXX flags is a way to prevent code that is NOT owner-id-aware from accessing the key.
// Think of it as "eating" a key completely: only same owner ID can access the key/button.
if (ImGui::TreeNode("3. Calling SetKeyOwner() with ImGuiInputFlags_LockXXX flags for non-owner-aware code"))
{
const ImGuiKey key = ImGuiKey_B;
const char* key_name = ImGui::GetKeyName(key);
ImGui::Text("Press '%s'", key_name);
static bool lock_this_frame = false;
static bool lock_until_release = false;
ImGui::Text("1st read:");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, false) ? "PRESSED!" : "..");
ImGui::Text("...when pressed, call SetKeyOwner() with:");
ImGui::Checkbox("ImGuiInputFlags_LockThisFrame", &lock_this_frame);
ImGui::Checkbox("ImGuiInputFlags_LockUntilRelease", &lock_until_release);
if (ImGui::IsKeyPressed(key, false) && (lock_this_frame || lock_until_release))
ImGui::SetKeyOwner(key, 0, (lock_this_frame ? ImGuiInputFlags_LockThisFrame : 0) | (lock_until_release ? ImGuiInputFlags_LockUntilRelease : 0));
// Assume this is another piece of code running later. The calls are not owner-aware,
// due to the lock they won't be able to see the key.
ImGui::Text("2nd read: (NOT owner-aware)");
ImGui::Text("- IsKeyDown(%s): %s", key_name, ImGui::IsKeyDown(key) ? "DOWN!" : "..");
ImGui::Text("- IsKeyPressed(%s): %s", key_name, ImGui::IsKeyPressed(key, false) ? "PRESSED!" : "..");
ImGui::TreePop();
}
// Miscellaneous examples
if (ImGui::TreeNode("Usage Scenarios"))
{
// We use colored buttons for the demo but this would generally apply to any widget.
const ImVec2 button_sz(60.0f, 60.0f);
const ImGuiColorEditFlags button_flags = ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop;
if (ImGui::TreeNode("1. Claiming Mouse Wheel"))
{
static float value1 = 0.0f;
ImGui::Text("%.2f", value1);
ImGui::SameLine();
HelpMarker("Hover button and use mouse wheel: window scrolling won't be activated.");
ImGui::ColorButton("Item1", ImVec4(0.4f, 0.4f, 0.8f, 1.0f), button_flags, button_sz);
if (ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY))
value1 += io.MouseWheel;
static float value2 = 0.0f;
ImGui::Text("%.2f", value2);
ImGui::SameLine();
HelpMarker("Hold button and use mouse wheel: window scrolling won't be activated.");
ImGui::ColorButton("Item2", ImVec4(0.4f, 0.4f, 0.8f, 1.0f), button_flags, button_sz);
if (ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY, ImGuiInputFlags_CondActive))
value2 += io.MouseWheel;
ImGui::TreePop();
}
if (ImGui::TreeNode("2. Claiming Alt key"))
{
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
static float spinner0 = 0.0f;
ImGui::Text("%.3f", spinner0);
ImGui::SameLine();
HelpMarker("Click, hold ALT drag to tweak value. Notice that Alt doesn't move focus to menu bar.");
ImGui::Button("Spin me", button_sz);
if (ImGui::IsItemActive())
{
ImGui::SetKeyOwner(ImGuiMod_Alt, ImGui::GetItemID());
if (ImGui::IsKeyDown(ImGuiMod_Alt)) // Poll on Active: we don't need to check for ownership of ImGuiMod_Alt since we know we unconditionally own it.
spinner0 += io.MouseDelta.x;
}
// When using of keys is conditioned by item being hovered or active,
// it creates a natural exclusivity, since only one item can be hovered or active.
// SetItemKeyOwner(...) is a shortcut for doing 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(..., GetItemID()); }'
static int value1 = 0;
ImGui::Text("%d", value1);
ImGui::SameLine();
HelpMarker("Item1 claims ALT key when Hovered or Active, counter increase when pressing ALT while Hovered.");
ImGui::Button("Item1", button_sz);
if (ImGui::SetItemKeyOwner(ImGuiMod_Alt)) // Claim Alt on Hover and Active
if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiMod_Alt, false)) // Poll on Hover: we don't need to check for ownership of ImGuiMod_Alt since we know we unconditionally own it.
value1++;
ImGui::TreePop();
}
// Routing options are only available for Shortcuts (not Key)
// Using shortcut functions with only ImGuiMod_Alt means that other modifiers e.g. CTRL+ALT+S won't be affected, which is often desirable.
// Here we use ImGuiInputFlags_RouteFocused which will claim the Alt shortcut when the window is focused.
// Notice that opening this node will claim Alt, therefore change the behavior of the key checks in section (2) above.
if (ImGui::TreeNode("3. Claiming Alt shortcut"))
{
// Using Shortcut() with ImGuiInputFlags_RouteFocused means we react when parent window is focused.
// - Passing 0 (== ImGuiKeyOwner_Any) means current location will be used to identify.
// As both calls are from the same location, both items will receive the shortcut.
// - Passing GetItemID() here means they both have their unique id,
// - Item2 will only receive the shortcut when parent window is focused.
// - Item3 will only receive the shortcut when active.
// Not passing an item id would use current location as id so both items will always receive shortcut.
static bool use_shared_owner = false;
ImGui::Checkbox("Item2 and Item3 use same owner/location", &use_shared_owner);
static int value2 = 0;
ImGui::Text("%d", value2);
ImGui::SameLine();
HelpMarker("Item2 reads ALT shortcut when its parent window is focused.");
ImGui::Button("Item2", button_sz);
if (ImGui::Shortcut(ImGuiMod_Alt, 0, use_shared_owner ? 0 : ImGui::GetItemID()))
value2++;
static int value3 = 0;
ImGui::Text("%d", value3);
ImGui::SameLine();
HelpMarker("Item3 reads ALT shortcut when its parent window is focused AND it is active. Therefore, only active previous button will get the shortcut");
ImGui::Button("Item3", button_sz);
if (ImGui::Shortcut(ImGuiMod_Alt, 0, use_shared_owner ? 0 : ImGui::GetItemID()))
value3++;
ImGui::TreePop();
}
if (ImGui::TreeNode("4. Claiming Home key globally"))
{
static bool enable_home_robbery = false;
static int home_presses = 0;
ImGui::Checkbox("Global steal ImGuiKey_Home", &enable_home_robbery);
ImGui::Text("Home presses: %d", home_presses);
if (enable_home_robbery)
{
// Claim ownership is enough to keep Key away from main library behavior or any owner-aware code.
// - We optionally use the ImGuiInputFlags_LockUntilRelease to keep key away from code that is not owner-aware,
// but Dear ImGui itself is so that's not technically needed (unless you are stealing from another piece of code).
ImGuiID robber_id = ImGui::GetID("Some Identifier");
ImGui::SetKeyOwner(ImGuiKey_Home, robber_id, ImGuiInputFlags_LockUntilRelease);
if (ImGui::IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, robber_id)) // We unconditionally own the key so no need to test for ownership
home_presses++;
}
ImGui::TreePop();
}
if (ImGui::TreeNode("5. Claiming CTRL+A shortcut globally"))
{
// Using a priority of ImGuiInputFlags_RouteGlobal + RouteOverActive means we takes away even from an active item (e.g. InputText)
// This is better covered in "Shortcut Routing basics" above.
static bool enable_ctrl_a_robbery = false;
static int ctrl_a_presses = 0;
ImGui::Checkbox("Global steal CTRL+A", &enable_ctrl_a_robbery);
ImGui::Text("CTRL+A presses: %d", ctrl_a_presses);
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, enable_ctrl_a_robbery ? ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive : ImGuiInputFlags_RouteAlways))
ctrl_a_presses++;
ImGui::TreePop();
}
if (ImGui::TreeNode("6. Disable ESC key from InputText()"))
{
static char buf[9];
ImGui::InputText("Text", buf, IM_ARRAYSIZE(buf));
// - If you don't need to use the key, you can use 'owner_id=0', 'flags=ImGuiInputFlags_LockXXX'
// as a convenience to hide the key from everyone.
// - If you need to use the key yourself, you need to use any arbitrary ID, and then use this ID to read the key.
// e.g. ImGui::SetKeyOwner(ImGuiKey_Escape, ImGui::GetID("robber")); + later use same ID to access the key.
if (ImGui::IsItemActive())
ImGui::SetKeyOwner(ImGuiKey_Escape, 0, ImGuiInputFlags_LockUntilRelease);
ImGui::TreePop();
}
if (ImGui::TreeNode("7. Claiming ESC key away from InputText()"))
{
static char buf[9];
ImGui::InputText("Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_EscapeClearsAll);
if (ImGui::IsItemActive())
{
// Using a route which is higher priority than one claimed the ActiveId
ImGuiID robber_id = ImGui::GetID("robber");
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive, robber_id))
{
strcpy(buf, "Esc!");
ImGui::ClearActiveID();
}
}
ImGui::TreePop();
}
if (ImGui::TreeNode("8. Claiming ESC away from nav logic (e.g. exiting a child)"))
{
ImGui::BeginChild("child", ImVec2(-FLT_MIN, 50), true);
ImGui::Button("Button in child");
if (ImGui::IsWindowFocused())
ImGui::SetKeyOwner(ImGuiKey_Escape, ImGui::GetID("")); // any id
ImGui::EndChild();
ImGui::TreePop();
}
if (ImGui::TreeNode("9. Claiming Tab, CTRL+Tab"))
{
static int mode = 0;
static int counter = 0;
HelpMarker("Showcasing many variants as a recap.\nPlease read code and comments carefully!");
const char* mode_names[] = { "None", "Disable Tab key (item)", "Disable Tab key (global)", "Disable CTRL+Tab (global)", "Disable CTRL+Tab (if focused)", "Read CTRL+Tab (global)", "Replace CTRL+Tab (global)", "Replace CTRL+Tab (if focused)" };
ImGui::Combo("Operation Mode", &mode, mode_names, IM_ARRAYSIZE(mode_names));
ImGui::Text("Counter = %d", counter);
switch (mode)
{
case 1:
// Item take ownership of Tab key when hovered/active (meaning ALL uses of Tab will be disabled, not just CTRL+Tab)
ImGui::Button("This Button Steals The Tab Key");
ImGui::SetItemKeyOwner(ImGuiKey_Tab);
ImGui::SameLine();
HelpMarker("While hovering or activating this button, TAB key is stolen (e.g. won't tab out into other systems)");
break;
case 2:
// Take ownership of Tab key (meaning ALL uses of Tab will be disabled, not just CTRL+Tab)
ImGui::SetKeyOwner(ImGuiKey_Tab, ImGui::GetID("some-id"));
break;
case 3:
// Disable CTRL+Tab shortcuts (global): assign an owner to steal the route to our two shortcuts
ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiKey_Tab, 0, ImGui::GetID("some-id"));
ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab, 0, ImGui::GetID("some-id"));
break;
case 4:
// Disable CTRL+Tab shortcuts (if focused): assign an owner to steal the route to our two shortcuts, applies focus testing so will only apply if window is in focus chain
ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiKey_Tab, ImGuiInputFlags_RouteFocused, ImGui::GetID("some-id"));
ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab, ImGuiInputFlags_RouteFocused, ImGui::GetID("some-id"));
break;
case 5:
// Read CTRL+Tab (global): reading keys without interfering with any behaviors (need to specify ImGuiInputFlags_RouteAlways as other policies will interfere)
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_Tab, ImGuiInputFlags_RouteAlways, ImGuiKeyOwner_Any))
counter++;
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab, ImGuiInputFlags_RouteAlways, ImGuiKeyOwner_Any))
counter--;
break;
case 6:
{
// Replace CTRL+Tab (global)
// - We steal the route and assign it to our ID (so core system won't access it). Our reading queries now need to specify that ID.
ImGuiID id = ImGui::GetID("My-Ctrl-Tab-Handler");
//ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiKey_Tab, id, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive);
//ImGui::SetShortcutRouting(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab, id, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive);
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_Tab, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive, id))
counter++; // You could perform some other action here.
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive, id))
counter--;
break;
}
case 7:
// Replace CTRL+Tab (if focused)
// - Passing ImGuiInputFlags_RouteFocused will test for focus and assign a route using a default owner id based on location (so we can use 0 as id)
// - This would also work if we replaced 0 with ImGui::GetID("My-Ctrl-Tab-Handler")
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_Tab))
counter++; // You could perform some other action here.
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab))
counter--;
break;
}
static char buf[8] = "";
ImGui::InputTextWithHint("Dummy", "(dummy input text to test effect of Tabbing)", buf, IM_ARRAYSIZE(buf));
ImGui::TreePop();
}
ImGui::TreePop();
}
ImGui::TreePop();
}
@@ -9130,6 +8782,22 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
EndTabItem();
}
if (BeginTabItem("Shadows"))
{
Text("Window shadows:");
ColorEdit4("Color", (float*)&style.Colors[ImGuiCol_WindowShadow], ImGuiColorEditFlags_AlphaBar);
SameLine();
HelpMarker("Same as 'WindowShadow' in Colors tab.");
SliderFloat("Size", &style.WindowShadowSize, 0.0f, 128.0f, "%.1f");
SameLine();
HelpMarker("Set shadow size to zero to disable shadows.");
SliderFloat("Offset distance", &style.WindowShadowOffsetDist, 0.0f, 64.0f, "%.0f");
SliderAngle("Offset angle", &style.WindowShadowOffsetAngle);
EndTabItem();
}
EndTabBar();
}
PopItemWidth();
@@ -10735,6 +10403,168 @@ static void ShowExampleAppCustomRendering(bool* p_open)
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Shadows"))
{
static float shadow_thickness = 40.0f;
static ImVec4 shadow_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
static bool shadow_filled = false;
static ImVec4 shape_color = ImVec4(0.9f, 0.6f, 0.3f, 1.0f);
static float shape_rounding = 0.0f;
static ImVec2 shadow_offset(0.0f, 0.0f);
static ImVec4 background_color = ImVec4(0.5f, 0.5f, 0.7f, 1.0f);
static bool wireframe = false;
static bool aa = true;
static int poly_shape_index = 0;
ImGui::Checkbox("Shadow filled", &shadow_filled);
ImGui::SameLine();
HelpMarker("This will fill the section behind the shape to shadow. It's often unnecessary and wasteful but provided for consistency.");
ImGui::Checkbox("Wireframe shapes", &wireframe);
ImGui::SameLine();
HelpMarker("This draws the shapes in wireframe so you can see the shadow underneath.");
ImGui::Checkbox("Anti-aliasing", &aa);
ImGui::DragFloat("Shadow Thickness", &shadow_thickness, 1.0f, 0.0f, 100.0f, "%.02f");
ImGui::SliderFloat2("Offset", (float*)&shadow_offset, -32.0f, 32.0f);
ImGui::SameLine();
HelpMarker("Note that currently circles/convex shapes do not support non-zero offsets for unfilled shadows.");
ImGui::ColorEdit4("Background Color", &background_color.x);
ImGui::ColorEdit4("Shadow Color", &shadow_color.x);
ImGui::ColorEdit4("Shape Color", &shape_color.x);
ImGui::DragFloat("Shape Rounding", &shape_rounding, 1.0f, 0.0f, 20.0f, "%.02f");
ImGui::Combo("Convex shape", &poly_shape_index, "Shape 1\0Shape 2\0Shape 3\0Shape 4\0Shape 4 (winding reversed)\0");
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImDrawListFlags old_flags = draw_list->Flags;
if (aa)
draw_list->Flags |= ~ImDrawListFlags_AntiAliasedFill;
else
draw_list->Flags &= ~ImDrawListFlags_AntiAliasedFill;
// Fill a strip of background
{
ImVec2 p = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(p, ImVec2(p.x + ImGui::GetContentRegionAvail().x, p.y + 200.0f), ImGui::GetColorU32(background_color));
}
// Rectangle
{
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::Dummy(ImVec2(200.0f, 200.0f));
ImVec2 r1(p.x + 50.0f, p.y + 50.0f);
ImVec2 r2(p.x + 150.0f, p.y + 150.0f);
ImDrawFlags draw_flags = shadow_filled ? ImDrawFlags_None : ImDrawFlags_ShadowCutOutShapeBackground;
draw_list->AddShadowRect(r1, r2, ImGui::GetColorU32(shadow_color), shadow_thickness, shadow_offset, draw_flags, shape_rounding);
if (wireframe)
draw_list->AddRect(r1, r2, ImGui::GetColorU32(shape_color), shape_rounding);
else
draw_list->AddRectFilled(r1, r2, ImGui::GetColorU32(shape_color), shape_rounding);
}
ImGui::SameLine();
// Circle
{
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::Dummy(ImVec2(200.0f, 200.0f));
// FIXME-SHADOWS: Offset forced to zero when shadow is not filled because it isn't supported
float off = 10.0f;
ImVec2 r1(p.x + 50.0f + off, p.y + 50.0f + off);
ImVec2 r2(p.x + 150.0f - off, p.y + 150.0f - off);
ImVec2 center(p.x + 100.0f, p.y + 100.0f);
ImDrawFlags draw_flags = shadow_filled ? ImDrawFlags_None : ImDrawFlags_ShadowCutOutShapeBackground;
draw_list->AddShadowCircle(center, 50.0f, ImGui::GetColorU32(shadow_color), shadow_thickness, shadow_filled ? shadow_offset : ImVec2(0.0f, 0.0f), draw_flags, 0);
if (wireframe)
draw_list->AddCircle(center, 50.0f, ImGui::GetColorU32(shape_color), 0);
else
draw_list->AddCircleFilled(center, 50.0f, ImGui::GetColorU32(shape_color), 0);
}
ImGui::SameLine();
// Convex shape
{
ImVec2 pos = ImGui::GetCursorScreenPos();
ImGui::Dummy(ImVec2(200.0f, 200.0f));
const ImVec2 poly_centre(pos.x + 50.0f, pos.y + 100.0f);
ImVec2 poly_points[8];
int poly_points_count = 0;
switch (poly_shape_index)
{
default:
case 0:
{
poly_points[0] = ImVec2(poly_centre.x - 32.0f, poly_centre.y);
poly_points[1] = ImVec2(poly_centre.x - 24.0f, poly_centre.y + 24.0f);
poly_points[2] = ImVec2(poly_centre.x, poly_centre.y + 32.0f);
poly_points[3] = ImVec2(poly_centre.x + 24.0f, poly_centre.y + 24.0f);
poly_points[4] = ImVec2(poly_centre.x + 32.0f, poly_centre.y);
poly_points[5] = ImVec2(poly_centre.x + 24.0f, poly_centre.y - 24.0f);
poly_points[6] = ImVec2(poly_centre.x, poly_centre.y - 32.0f);
poly_points[7] = ImVec2(poly_centre.x - 32.0f, poly_centre.y - 32.0f);
poly_points_count = 8;
break;
}
case 1:
{
poly_points[0] = ImVec2(poly_centre.x + 40.0f, poly_centre.y - 20.0f);
poly_points[1] = ImVec2(poly_centre.x, poly_centre.y + 32.0f);
poly_points[2] = ImVec2(poly_centre.x - 24.0f, poly_centre.y - 32.0f);
poly_points_count = 3;
break;
}
case 2:
{
poly_points[0] = ImVec2(poly_centre.x - 32.0f, poly_centre.y);
poly_points[1] = ImVec2(poly_centre.x, poly_centre.y + 32.0f);
poly_points[2] = ImVec2(poly_centre.x + 32.0f, poly_centre.y);
poly_points[3] = ImVec2(poly_centre.x, poly_centre.y - 32.0f);
poly_points_count = 4;
break;
}
case 3:
{
poly_points[0] = ImVec2(poly_centre.x - 4.0f, poly_centre.y - 20.0f);
poly_points[1] = ImVec2(poly_centre.x + 12.0f, poly_centre.y + 2.0f);
poly_points[2] = ImVec2(poly_centre.x + 8.0f, poly_centre.y + 16.0f);
poly_points[3] = ImVec2(poly_centre.x, poly_centre.y + 32.0f);
poly_points[4] = ImVec2(poly_centre.x - 16.0f, poly_centre.y - 32.0f);
poly_points_count = 5;
break;
}
case 4: // Same as test case 3 but with reversed winding
{
poly_points[0] = ImVec2(poly_centre.x - 16.0f, poly_centre.y - 32.0f);
poly_points[1] = ImVec2(poly_centre.x, poly_centre.y + 32.0f);
poly_points[2] = ImVec2(poly_centre.x + 8.0f, poly_centre.y + 16.0f);
poly_points[3] = ImVec2(poly_centre.x + 12.0f, poly_centre.y + 2.0f);
poly_points[4] = ImVec2(poly_centre.x - 4.0f, poly_centre.y - 20.0f);
poly_points_count = 5;
break;
}
}
// FIXME-SHADOWS: Offset forced to zero when shadow is not filled because it isn't supported
ImDrawFlags draw_flags = shadow_filled ? ImDrawFlags_None : ImDrawFlags_ShadowCutOutShapeBackground;
draw_list->AddShadowConvexPoly(poly_points, poly_points_count, ImGui::GetColorU32(shadow_color), shadow_thickness, shadow_filled ? shadow_offset : ImVec2(0.0f, 0.0f), draw_flags);
if (wireframe)
draw_list->AddPolyline(poly_points, poly_points_count, ImGui::GetColorU32(shape_color), 1.0f, ImDrawFlags_Closed);
else
draw_list->AddConvexPolyFilled(poly_points, poly_points_count, ImGui::GetColorU32(shape_color));
}
draw_list->Flags = old_flags;
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("BG/FG draw lists"))
{
IMGUI_DEMO_MARKER("Examples/Custom rendering/BG & FG draw lists");

File diff suppressed because it is too large Load Diff

View File

@@ -171,6 +171,7 @@ struct ImGuiOldColumns; // Storage data for a columns set for legacy
struct ImGuiPopupData; // Storage for current popup stack
struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file
struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it
struct ImGuiStyleShadowTexConfig; // Shadow Texture baking config
struct ImGuiStyleVarInfo; // Style variable information (e.g. to access style variables from an enum)
struct ImGuiTabBar; // Storage for a tab bar
struct ImGuiTabItem; // Storage for a tab item (within a tab bar)
@@ -524,6 +525,7 @@ inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return
inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; }
inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); }
inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); }
inline float ImLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImSqrt(d); return fail_value; }
inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; }
inline float ImTrunc(float f) { return (float)(int)(f); }
inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); }
@@ -885,6 +887,9 @@ struct IMGUI_API ImDrawListSharedData
float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo()
ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead)
int* ShadowRectIds; // IDs of rects for shadow texture (2 entries)
const ImVec4* ShadowRectUvs; // UV coordinates for shadow texture (10 entries)
ImDrawListSharedData();
~ImDrawListSharedData();
void SetCircleTessellationMaxError(float max_error);

View File

@@ -9268,10 +9268,14 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im
}
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
// Create window
PushStyleColor(ImGuiCol_WindowShadow, ImVec4(0, 0, 0, 0));
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
bool is_open = Begin(name, NULL, window_flags);
PopStyleVar(2);
PopStyleColor();
return is_open;
}