Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,28 @@ Four things to know before changing any of it:
`ImGuiWidgets.Viewport3D` (sub-issue 4). `TryGetRenderer3D` therefore answers `false` in a real
desktop application today and `true` under a headless harness.

### Mouse cursors

Dear ImGui only records which cursor a frame wants; a platform backend has to show it. Until
`PlatformCursor` (in `ImGui.App/ImGuiController/`), nothing did, so every `ImGui.SetMouseCursor` a
widget made was dropped and the pointer stayed an arrow everywhere. `ImGuiController.Render` now
hands `ImGui.GetMouseCursor()` to Silk.NET's `ICursor` after each frame, and declares
`ImGuiBackendFlags.HasMouseCursors`, which is also what turns on ImGui's resize-from-window-edges.
It writes only when the cursor changes, honours `ImGuiConfigFlags.NoMouseCursorChange` and
`io.MouseDrawCursor`, and leaves a pointer the application has captured (`CursorMode.Disabled` or
`Raw`) alone.

The headless context declares the same flag, and `ImGuiAppHarness.MouseCursor` reports what the
last frame asked for, so a widget's cursor is a UI-test assertion like anything else.

Widgets choose by what a press does, through `WidgetCursor.OnLastItem`: `Hand` for a custom click
target (switch, chip, segment, star, key, step cell, page dot, an icon with a click handler),
`ResizeEw`/`ResizeNs` for a one-axis drag (range slider, handle track, fader, knob, the colour
wheel's master bar), and `ResizeAll` for a free drag (XY pad, colour wheel, a curve point, a
canvas pan). Plain `ImGui.Button`s, including `ToolbarButton`, keep the arrow, as ImGui's own
buttons do, and a read-only or click-less instance keeps it too. Text fields get the beam from
ImGui itself.

### Deferred Drawing (dialogs and docked windows)

Hexa's dialogs and `DockedWindow` are stateful: `Show()` registers the instance with a static
Expand Down
4 changes: 4 additions & 0 deletions ImGui.App.Testing/HeadlessImGuiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public HeadlessImGuiContext(int width, int height, float dpiScale, SoftwareRende
// through the frame's texture list, which is what lets it rasterize new glyph sizes on
// demand. A harness that baked one atlas up front could not exercise that.
io.BackendFlags |= ImGuiBackendFlags.RendererHasTextures;

// The desktop backend shows ImGui's cursor, and ImGui behaves differently when nothing
// does (it stops offering window edges to resize), so the harness claims the same.
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
}

/// <summary>Gets the ImGui IO block for this context.</summary>
Expand Down
10 changes: 10 additions & 0 deletions ImGui.App.Testing/ImGuiAppHarness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace ktsu.ImGui.App.Testing;

using System;

using Hexa.NET.ImGui;

using ktsu.ImGui.App;
using ktsu.ImGui.Probes;

Expand Down Expand Up @@ -43,6 +45,13 @@ private ImGuiAppHarness(ImGuiAppConfig config, HarnessOptions options)
/// <summary>Gets the number of frames advanced so far.</summary>
public int FrameCount { get; private set; }

/// <summary>
/// Gets the mouse cursor the most recent frame asked for, as <c>ImGui.GetMouseCursor()</c>
/// reported it once the frame ended. This is what the desktop backend shows on the window's
/// pointer, so a test asserts a widget's cursor here.
/// </summary>
public ImGuiMouseCursor MouseCursor { get; private set; }

/// <summary>Gets the render target holding the most recently rendered frame.</summary>
public Bitmap32 Target => renderer.Target;

Expand Down Expand Up @@ -159,6 +168,7 @@ public void Step(int frames)
ImGuiApp.RenderFrameContents(config, Options.FrameDelta);

context.EndFrame();
MouseCursor = ImGui.GetMouseCursor();
}
catch (Exception error) when (error is not HarnessFrameException)
{
Expand Down
25 changes: 25 additions & 0 deletions ImGui.App/ImGuiController/ImGuiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal sealed class ImGuiController : IRendererBackend
internal readonly List<char> _pressedChars = [];
internal IKeyboard? _keyboard;
internal IMouse? _mouse;
private readonly PlatformCursor _platformCursor = new();

internal int _attribLocationTex;
internal int _attribLocationProjMtx;
Expand Down Expand Up @@ -110,6 +111,11 @@ public ImGuiController(GL gl, IView view, IInputContext input, ImGuiFontConfig?
// than only at sizes registered up front.
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasTextures;

// HasMouseCursors tells ImGui its cursor requests reach the window; without it ImGui also
// turns off resizing windows by their edges, since nothing would show the user an edge is
// there. ApplyMouseCursor below is what keeps that promise.
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;

DebugLogger.Log("ImGuiController: Creating device resources");
CreateDeviceResources();
DebugLogger.Log("ImGuiController: Device resources created");
Expand Down Expand Up @@ -278,10 +284,29 @@ internal void Render()
{
_frameBegun = false;
ImGui.Render();
ApplyMouseCursor();
RenderDrawData(ImGui.GetDrawData());
}
}

/// <summary>
/// Carries the cursor the frame just decided on to the window's pointer.
/// </summary>
private void ApplyMouseCursor()
{
if (_mouse is null)
{
return;
}

ImGuiIOPtr io = ImGui.GetIO();
_platformCursor.Apply(
_mouse.Cursor,
ImGui.GetMouseCursor(),
io.MouseDrawCursor,
(io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0);
}

/// <inheritdoc />
[SuppressMessage(
"Reliability",
Expand Down
84 changes: 84 additions & 0 deletions ImGui.App/ImGuiController/PlatformCursor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.ImGui.App.ImGuiController;

using Hexa.NET.ImGui;
using Silk.NET.Input;

/// <summary>
/// Shows the cursor a frame asked for on the operating system's own pointer.
/// </summary>
/// <remarks>
/// Dear ImGui decides a cursor every frame, from a hovered resize grip or text field or from a
/// widget's own <c>ImGui.SetMouseCursor</c> call, but it only records the decision; a platform
/// backend has to carry it to the window. Without this, every cursor a widget chose was dropped
/// and the pointer stayed an arrow over a splitter as over everything else.
/// </remarks>
internal sealed class PlatformCursor
{
private ImGuiMouseCursor? applied;

/// <summary>
/// Maps a Dear ImGui cursor to the standard cursor that looks like it.
/// </summary>
/// <param name="cursor">The cursor the frame asked for.</param>
/// <returns>The standard cursor, or null when the pointer should be hidden.</returns>
internal static StandardCursor? ToStandardCursor(ImGuiMouseCursor cursor) => cursor switch
{
ImGuiMouseCursor.None => null,
ImGuiMouseCursor.TextInput => StandardCursor.IBeam,
ImGuiMouseCursor.ResizeAll => StandardCursor.ResizeAll,
ImGuiMouseCursor.ResizeNs => StandardCursor.VResize,
ImGuiMouseCursor.ResizeEw => StandardCursor.HResize,
ImGuiMouseCursor.ResizeNesw => StandardCursor.NeswResize,
ImGuiMouseCursor.ResizeNwse => StandardCursor.NwseResize,
ImGuiMouseCursor.Hand => StandardCursor.Hand,
ImGuiMouseCursor.NotAllowed => StandardCursor.NotAllowed,
ImGuiMouseCursor.Wait => StandardCursor.Wait,
ImGuiMouseCursor.Progress => StandardCursor.WaitArrow,
_ => StandardCursor.Arrow,
};

/// <summary>
/// Applies the cursor the frame asked for, writing to the platform only when it changed.
/// </summary>
/// <param name="cursor">The platform cursor of the window's mouse.</param>
/// <param name="requested">What <c>ImGui.GetMouseCursor()</c> reported once the frame ended.</param>
/// <param name="imGuiDrawsCursor">True when <c>io.MouseDrawCursor</c> is set, so ImGui draws the pointer itself.</param>
/// <param name="changesDisabled">True when <c>ImGuiConfigFlags.NoMouseCursorChange</c> is set.</param>
internal void Apply(ICursor cursor, ImGuiMouseCursor requested, bool imGuiDrawsCursor, bool changesDisabled)
{
if (changesDisabled)
{
return;
}

// An application that captured the mouse (a camera that hides and locks the pointer) has
// taken it over; showing a cursor shape there would undo that.
if (cursor.CursorMode is CursorMode.Disabled or CursorMode.Raw)
{
applied = null;
return;
}

// ImGui drawing its own pointer means the system one would be a second arrow on top of it.
ImGuiMouseCursor effective = imGuiDrawsCursor ? ImGuiMouseCursor.None : requested;
if (applied == effective)
{
return;
}

applied = effective;

StandardCursor? standard = ToStandardCursor(effective);
if (standard is null)
{
cursor.CursorMode = CursorMode.Hidden;
return;
}

cursor.CursorMode = CursorMode.Normal;
cursor.Type = CursorType.Standard;
cursor.StandardCursor = cursor.IsSupported(standard.Value) ? standard.Value : StandardCursor.Default;
}
}
1 change: 1 addition & 0 deletions ImGui.App/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ImGuiApp is a .NET library that provides application scaffolding for [Dear ImGui
- **Context Handling**: Automatic OpenGL context change detection and texture reloading
- **Lifecycle Callbacks**: Customizable delegate callbacks for application events
- **Menu System**: Easy-to-use API for creating application menus
- **Mouse Cursors**: The cursor a frame asks for through `ImGui.SetMouseCursor` (resize, hand, text beam, move) is shown on the system pointer
- **Positioning Guards**: Offscreen positioning checks to keep windows visible
- **Modern .NET**: Supports .NET 8, 9, and 10
- **Active Development**: Open-source and actively maintained
Expand Down
6 changes: 6 additions & 0 deletions ImGui.Widgets/ChannelFader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ private static bool Interact(string label, ref float gainDb, float top, FaderLay
{
bool changed = Drag(ref gainDb, top, layout, state, position);

// Only the track takes a drag; the meter beside it and the readout under it do not.
if (state.IsDragging || (ImGui.IsItemHovered() && layout.TrackContains(ImGui.GetIO().MousePos)))
{
ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeNs);
}

if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && !IsUnity(gainDb))
{
gainDb = 0f;
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/Chip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static bool Draw(string label, bool selected, bool closable, out bool clo
Vector2 origin = ImGui.GetCursorScreenPos();
ImGui.InvisibleButton(label, new Vector2(width, height));
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);

bool hovered = ImGui.IsItemHovered();
bool bodyClicked = ImGui.IsItemClicked();
Expand Down
2 changes: 2 additions & 0 deletions ImGui.Widgets/ColorWheel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public static bool ColorWheel(string label, ref ColorWheelValue value, float dia
Vector2 min = ImGui.GetCursorScreenPos();
ImGui.InvisibleButton(label, new Vector2(side, side));
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.ResizeAll);

float radius = side * 0.5f;
Vector2 center = min + new Vector2(radius, radius);
Expand Down Expand Up @@ -222,6 +223,7 @@ internal static bool MasterSlider(ColorWheelState state, ref ColorWheelValue val
Vector2 min = ImGui.GetCursorScreenPos();
ImGui.InvisibleButton("master", new Vector2(width, height));
ImGuiProbes.MarkItem("master");
WidgetCursor.OnLastItem(ImGuiMouseCursor.ResizeEw);

ImGuiIOPtr io = ImGui.GetIO();
float half = MathF.Max(1.0f, width * 0.5f);
Expand Down
7 changes: 7 additions & 0 deletions ImGui.Widgets/CurveTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
/// corrupt, and its add gesture does not index a point before checking there is one.
/// </para>
/// </remarks>
public static bool CurveTrack(

Check warning on line 66 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 10 parameters, which is greater than the 7 authorized.

Check warning on line 66 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 10 parameters, which is greater than the 7 authorized.
string label,
IList<Vector2> points,
Func<float, float> sample,
Expand All @@ -90,7 +90,7 @@
/// </remarks>
private const float SampleSpacing = 2.0f;

public static bool Draw(

Check warning on line 93 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 10 parameters, which is greater than the 7 authorized.

Check warning on line 93 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 10 parameters, which is greater than the 7 authorized.
string label,
IList<Vector2> points,
Func<float, float> sample,
Expand Down Expand Up @@ -235,7 +235,7 @@
}
}

private static void DrawPoints(

Check warning on line 238 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 8 parameters, which is greater than the 7 authorized.

Check warning on line 238 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 8 parameters, which is greater than the 7 authorized.
ImDrawListPtr drawList,
IList<Vector2> points,
Vector2 min,
Expand All @@ -254,7 +254,7 @@
}
}

private static bool Interact(

Check warning on line 257 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 9 parameters, which is greater than the 7 authorized.

Check warning on line 257 in ImGui.Widgets/CurveTrack.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Method has 9 parameters, which is greater than the 7 authorized.
string label,
IList<Vector2> points,
Vector2 min,
Expand Down Expand Up @@ -315,6 +315,13 @@
}
}

// The move cursor marks a point that can be picked up; the open track keeps the arrow,
// since a press there adds a point rather than moving one.
if (ImGui.IsItemActive() || (ImGui.IsItemHovered() && new CurveTrackState().Activate(points, pointer, grabInValue)))
{
ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeAll);
}

return changed;
}
}
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/HandleTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public static bool Draw(
ImGui.SetCursorScreenPos(rectMin);
ImGui.InvisibleButton(label, new Vector2(MathF.Max(rectMax.X - rectMin.X, 1.0f), height));
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.ResizeEw);
ImGui.SetCursorScreenPos(cursor);

uint id = ImGui.GetID(label);
Expand Down
7 changes: 7 additions & 0 deletions ImGui.Widgets/Icon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
/// <summary>
/// The tooltip to display.
/// </summary>
public string Tooltip { get; init; } = string.Empty;

Check warning on line 52 in ImGui.Widgets/Icon.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Rename this property to not shadow the outer class' member with the same name.

Check warning on line 52 in ImGui.Widgets/Icon.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Rename this property to not shadow the outer class' member with the same name.

/// <summary>
/// Gets or sets the action to be performed on click.
Expand Down Expand Up @@ -215,7 +215,7 @@
/// </summary>
internal static class IconImpl
{
internal static bool Show(string textBlock, nint textureId, Vector2 imageSize, IconAlignment iconAlignment, IconOptions options)

Check failure on line 218 in ImGui.Widgets/Icon.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_ImGuiApp&issues=AaEVslGtEc5jG41LUSEd&open=AaEVslGtEc5jG41LUSEd&pullRequest=594
{
Ensure.NotNull(textBlock);
Ensure.NotNull(options);
Expand Down Expand Up @@ -257,6 +257,13 @@
bool isRightMouseClicked = ImGui.IsMouseClicked(ImGuiMouseButton.Right);
bool isRightMouseReleased = ImGui.IsMouseReleased(ImGuiMouseButton.Right);

// Only an icon that does something on a left click says so; one with just a tooltip or a
// context menu keeps the arrow.
if (isHovered && (options.OnClick is not null || options.OnDoubleClick is not null))
{
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
}

if (!string.IsNullOrEmpty(options.Tooltip))
{
ImGui.SetItemTooltip(options.Tooltip);
Expand Down
4 changes: 4 additions & 0 deletions ImGui.Widgets/ImageCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public static void ImageCanvas(string id, nint textureId, Vector2 imageSize, Ima
if ((gesture.Gestures & GestureFlags.Pan) != 0)
{
state.PanBy(ImGui.GetIO().MouseDelta);

// Only once the drag is a pan: a click on the canvas, and an overlay such as a crop that
// takes the hover, keep their own cursor.
ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeAll);
}

if (gesture.DoubleTapped)
Expand Down
3 changes: 3 additions & 0 deletions ImGui.Widgets/Knob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ public KnobInternal(string label_, ImGuiDataType dataType, ref TDataType value,
ImGui.InvisibleButton(label_, new(Radius * 2.0f, Radius * 2.0f));
ImGuiProbes.MarkItem(label_);

// A knob turns by dragging along one axis, not by circling it, so the cursor names the axis.
WidgetCursor.OnLastItem(flags.HasFlag(ImGuiKnobOptions.DragHorizontal) ? ImGuiMouseCursor.ResizeEw : ImGuiMouseCursor.ResizeNs);

ValueChanged = DragBehavior(dataType, ref value, vMin, vMax, speed, format, flags);

// Compute the normalized value AFTER the drag so the indicator reflects
Expand Down
4 changes: 3 additions & 1 deletion ImGui.Widgets/PageIndicator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ internal static int Draw(string id, int currentPage, int pageCount, bool interac
if (interactive)
{
ImGui.SetCursorScreenPos(new Vector2(center.X - activeRadius, origin.Y));
if (ImGui.InvisibleButton(string.Create(CultureInfo.InvariantCulture, $"{id}_dot{i}"), new Vector2(activeRadius * 2.0f, height)))
bool dotClicked = ImGui.InvisibleButton(string.Create(CultureInfo.InvariantCulture, $"{id}_dot{i}"), new Vector2(activeRadius * 2.0f, height));
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);
if (dotClicked)
{
selected = i;
}
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/PianoKeyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public static PianoKeyEvent Draw(string label, PianoKeyboardLayout layout, Vecto
Vector2 extent = new(width, height);
ImGui.InvisibleButton(label, extent);
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);

Vector2 max = min + extent;
float whiteWidth = width / layout.WhiteKeyCount;
Expand Down
3 changes: 3 additions & 0 deletions ImGui.Widgets/RangeSlider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public static bool Draw(string label, ref float lower, ref float upper, float mi
ImGui.InvisibleButton(label, new Vector2(width, height));
ImGuiProbes.MarkItem(label);

// A press anywhere on the track takes the nearer handle, so the whole track is a handle.
WidgetCursor.OnLastItem(ImGuiMouseCursor.ResizeEw);

float trackMinX = origin.X + grabRadius;
float trackMaxX = origin.X + width - grabRadius;
float trackY = origin.Y + (height * 0.5f);
Expand Down
4 changes: 4 additions & 0 deletions ImGui.Widgets/Rating.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ internal static bool Draw(string id, ref float value, int starCount, bool allowH
bool clicked = ImGui.InvisibleButton(id, new Vector2(totalWidth, starSize));
ImGuiProbes.MarkItem(id);
bool hovered = !readOnly && ImGui.IsItemHovered();
if (!readOnly)
{
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);
}

float displayValue = value;
bool changed = false;
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/SegmentedControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public static bool Draw(string label, ref int selectedIndex, IReadOnlyList<strin
Vector2 origin = ImGui.GetCursorScreenPos();
ImGui.InvisibleButton(label, new Vector2(totalWidth, height));
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);

bool changed = false;
if (ImGui.IsItemActive() && ImGui.IsItemHovered())
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/StepGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public static bool Draw(string label, Span<bool> steps, int rows, int stepCount,
ImGui.SetCursorScreenPos(gridOrigin);
ImGui.InvisibleButton(label, layout.Size, ImGuiButtonFlags.MouseButtonLeft);
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);

bool hovered = ImGui.IsItemHovered();
Vector2 local = ImGui.GetIO().MousePos - gridOrigin;
Expand Down
1 change: 1 addition & 0 deletions ImGui.Widgets/Switch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static bool Draw(string label, ref bool value)
Vector2 origin = ImGui.GetCursorScreenPos();
ImGui.InvisibleButton(label, new Vector2(width, height));
ImGuiProbes.MarkItem(label);
WidgetCursor.OnLastItem(ImGuiMouseCursor.Hand);

bool changed = false;
if (ImGui.IsItemClicked())
Expand Down
Loading
Loading