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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This is the **ktsu ImGui Suite**, a collection of .NET libraries for building De
### Libraries

- **ImGui.App** (`ktsu.ImGui.App`) - Application foundation with windowing, rendering, font/texture management, PID frame limiting, DPI awareness. Image decoding is self-contained (`ImGui.App/Images/`), so the package carries no imaging dependency; see [Image decoding](#image-decoding) below.
- **ImGui.Widgets** (`ktsu.ImGui.Widgets`) - Custom UI components, grouped as the README's feature list groups them: input and controls (`Switch`, `SegmentedControl`, `Stepper`, `TimecodeField` over the ImGui-free `Timecode`/`TimecodeRate`, `RangeSlider`, `ChannelFader`/`FaderTaper`, `XYPad`, `ColorWheel`/`LiftGammaGain`, `Knob`/`KnobWithDrag`, `Rating`, `Chip`/`ChipGroup`, `PinInput`, `SearchBox`/`SearchBoxRanked`, `Combo`, `StepGrid`, `PianoKeyboard` over a caller-owned `PianoKeyboardLayout`, `TransportScrubber`); display and status (`Avatar`, `Badge`/`BadgeDot`, `ColorIndicator`, `Icon`, `Text`, `Image`, `PageIndicator`); progress and loading (`RadialProgressBar` with `RadialCountdown`/`RadialCountUp`, `SkeletonLine`/`SkeletonRect`/`SkeletonCircle`); data and signals (`Histogram`, `FrameTimeGraph` over a caller-owned `FrameTimeHistory`, `HandleTrack`, `CurveTrack`, `DbMeter`, `GainReductionMeter`, `CorrelationMeter`/`StereoMetersState`, `Goniometer`, `Scope`, `Waveform` (zoomable over a caller-owned `TimelineView` and a `WaveformPeakSource`/`WaveformPeakCache`), `SpectrumAnalyzer` over a caller-owned `SpectrumAnalyzerState`, `EnvelopeEditor` over an `Envelope`, `ParametricEq` over `EqBand`s with `EqResponse` for the RBJ biquad response, and image and colour (`LevelsControl` over a caller-owned `LevelsAdjustment`, `AssetBrowser` over a caller-owned `AssetBrowserState`, whose `Apply` is the transfer function the widget draws, `GradientEditor` over a caller-owned list of `GradientStop`, with `SampleGradient` as the evaluator the bar `Toolbar`/`ToolbarSeparator` with `ToolbarButton`/`ToolbarToggleButton` (glyph and label in one frame, `ToolbarButtonLayout` picking leading, above-and-centred, glyph-only or label-only, the arithmetic in the ImGui-free `ToolbarGeometry`; `GlyphSize` on the toolbar or a button draws icons at their own pixel size beside normal-size labels), is drawn with, `SwatchPalette`, `ImageCompare`, `CropOverlay` over an `ImageCanvas`, `PixelLoupe` beside an `ImageCanvas`); the `LogFrequencyAxis` both share); layout and containers (`DividerContainer`/`DividerZone`, `Grid`, `TabPanel`, `Card`, `Tree`, `ImageCanvas`, `PropertyGrid`, `OverlayHost`/`OverlayLayer`, `ScopedDisable`, `ScopedId`); and motion and gestures (`Tween`, `Spring`, `Easing`, `InertialScroll`, `GestureDetector`/`GestureMachine`). Also thin adapters delegating to `Hexa.NET.ImGui.Widgets`: `Spinner`, `BufferingBar`, `HorizontalSplitter`/`VerticalSplitter`, `ToggleSwitch`/`ToggleButton`/`TransparentButton`/`InlineButton`, `IconTreeNode`, `EnumCombo`, `TextCenteredV`/`TextCenteredH`/`TextCenteredVH`, `ImageCenteredV`/`ImageCenteredH`/`ImageCenteredVH`/`ImageScaleTo`, `Tooltip`, `Breadcrumb`, `DatePicker`/`YearPicker`, `FlameGraph`, `FileTreeView`, `OpenFileDialog`/`SaveFileDialog`/`OpenFolderDialog`, `RenameDialog`, `DialogMessageBox`/`ShowMessageBox`, `DockedWindow`. Seven of these look like duplicates of an existing ktsu widget; most are not, and the two that are have a recommended survivor — see [Hexa-backed vs ktsu widgets](#hexa-backed-vs-ktsu-widgets) below for the pair-by-pair verdict. `DatePicker` and `FileTreeView` need a Material Icons font registered via `FontHelper.AddCustomFont(io, data, size, FontHelper.GetMaterialIconRanges(), mergeWithPrevious: true)` (not `ImGuiAppConfig.Fonts`, which applies the Nerd Font mapping); see `examples/ImGuiAppDemo`. `YearPicker` needs no icon font. `OpenFileDialog`, `SaveFileDialog` and `OpenFolderDialog` need the same Material Icons font, for their toolbar, breadcrumb and file-tree glyphs; `RenameDialog`, `DialogMessageBox` and `ShowMessageBox` need none. `DockedWindow` composes Hexa's `ImWindow` internally rather than inheriting it — subclass it, override `Title` and `DrawContent()`, then call `Show()`/`Close()`. All of the dialogs and `DockedWindow` require a per-frame deferred-drawing pump; see [Deferred Drawing](#deferred-drawing-dialogs-and-docked-windows) below. Also includes callback-driven editors: `Sequencer`, `SequenceSource`, `CurveEditor`, `CurveSource`, `CurveData`, `BezierEditor`. Unlike the dialogs above, none of these need a deferred-drawing pump; see [Callback-driven editors](#callback-driven-editors) below.
- **ImGui.Widgets** (`ktsu.ImGui.Widgets`) - Custom UI components, grouped as the README's feature list groups them: input and controls (`Switch`, `SegmentedControl`, `Stepper`, `TimecodeField` over the ImGui-free `Timecode`/`TimecodeRate`, `RangeSlider`, `ChannelFader`/`FaderTaper`, `XYPad`, `ColorWheel`/`LiftGammaGain`, `Knob`/`KnobWithDrag`, `Rating`, `Chip`/`ChipGroup`, `PinInput`, `SearchBox`/`SearchBoxRanked`, `Combo`, `StepGrid`, `PianoKeyboard` over a caller-owned `PianoKeyboardLayout`, `TransportScrubber`); display and status (`Avatar`, `Badge`/`BadgeDot`, `ColorIndicator`, `Icon`, `Text`, `Image`, `PageIndicator`); progress and loading (`RadialProgressBar` with `RadialCountdown`/`RadialCountUp`, `SkeletonLine`/`SkeletonRect`/`SkeletonCircle`); data and signals (`Histogram`, `FrameTimeGraph` over a caller-owned `FrameTimeHistory`, `HandleTrack`, `CurveTrack`, `DbMeter`, `GainReductionMeter`, `CorrelationMeter`/`StereoMetersState`, `Goniometer`, `Scope`, `Waveform` (zoomable over a caller-owned `TimelineView` and a `WaveformPeakSource`/`WaveformPeakCache`), `SpectrumAnalyzer` over a caller-owned `SpectrumAnalyzerState`, `EnvelopeEditor` over an `Envelope`, `ParametricEq` over `EqBand`s with `EqResponse` for the RBJ biquad response, and image and colour (`LevelsControl` over a caller-owned `LevelsAdjustment`, `AssetBrowser` over a caller-owned `AssetBrowserState`, whose `Apply` is the transfer function the widget draws, `GradientEditor` over a caller-owned list of `GradientStop`, with `SampleGradient` as the evaluator the bar `Toolbar`/`ToolbarSeparator` with `ToolbarButton`/`ToolbarToggleButton` (glyph and label in one frame, `ToolbarButtonLayout` picking leading, above-and-centred, glyph-only or label-only, the arithmetic in the ImGui-free `ToolbarGeometry`; `GlyphSize` on the toolbar or a button draws icons at their own pixel size beside normal-size labels), is drawn with, `SwatchPalette`, `ImageCompare`, `CropOverlay` over an `ImageCanvas`, `PixelLoupe` beside an `ImageCanvas`); the `LogFrequencyAxis` both share); layout and containers (`DividerContainer`/`DividerZone`, `Grid`, `TabPanel`, `Card`, `Tree`, `ReorderableTree` (rows the caller draws, dragged to reorder and re-parent; reports a `ReorderableTreeMove` whose index counts the parent's children before the move, resolved by the ImGui-free `ReorderableTreeState`), `ImageCanvas`, `PropertyGrid`, `OverlayHost`/`OverlayLayer`, `ScopedDisable`, `ScopedId`); and motion and gestures (`Tween`, `Spring`, `Easing`, `InertialScroll`, `GestureDetector`/`GestureMachine`). Also thin adapters delegating to `Hexa.NET.ImGui.Widgets`: `Spinner`, `BufferingBar`, `HorizontalSplitter`/`VerticalSplitter`, `ToggleSwitch`/`ToggleButton`/`TransparentButton`/`InlineButton`, `IconTreeNode`, `EnumCombo`, `TextCenteredV`/`TextCenteredH`/`TextCenteredVH`, `ImageCenteredV`/`ImageCenteredH`/`ImageCenteredVH`/`ImageScaleTo`, `Tooltip`, `Breadcrumb`, `DatePicker`/`YearPicker`, `FlameGraph`, `FileTreeView`, `OpenFileDialog`/`SaveFileDialog`/`OpenFolderDialog`, `RenameDialog`, `DialogMessageBox`/`ShowMessageBox`, `DockedWindow`. Seven of these look like duplicates of an existing ktsu widget; most are not, and the two that are have a recommended survivor — see [Hexa-backed vs ktsu widgets](#hexa-backed-vs-ktsu-widgets) below for the pair-by-pair verdict. `DatePicker` and `FileTreeView` need a Material Icons font registered via `FontHelper.AddCustomFont(io, data, size, FontHelper.GetMaterialIconRanges(), mergeWithPrevious: true)` (not `ImGuiAppConfig.Fonts`, which applies the Nerd Font mapping); see `examples/ImGuiAppDemo`. `YearPicker` needs no icon font. `OpenFileDialog`, `SaveFileDialog` and `OpenFolderDialog` need the same Material Icons font, for their toolbar, breadcrumb and file-tree glyphs; `RenameDialog`, `DialogMessageBox` and `ShowMessageBox` need none. `DockedWindow` composes Hexa's `ImWindow` internally rather than inheriting it — subclass it, override `Title` and `DrawContent()`, then call `Show()`/`Close()`. All of the dialogs and `DockedWindow` require a per-frame deferred-drawing pump; see [Deferred Drawing](#deferred-drawing-dialogs-and-docked-windows) below. Also includes callback-driven editors: `Sequencer`, `SequenceSource`, `CurveEditor`, `CurveSource`, `CurveData`, `BezierEditor`. Unlike the dialogs above, none of these need a deferred-drawing pump; see [Callback-driven editors](#callback-driven-editors) below.
- **ImGui.Popups** (`ktsu.ImGui.Popups`) - Modal dialogs: MessageOK, Prompt, InputString/Int/Float, FilesystemBrowser, SearchableList
- **ImGui.Color** (`ktsu.ImGui.Color`) - Bridge between `ktsu.Semantics.Color` and ImGui. Colors are held as the semantic `Color` (linear) and `Srgb` types and converted only at the ImGui seam: `ColorImGuiExtensions` (`ToImColor`/`FromImColor`, `ToImGuiVector4`, `ToImGuiU32`) and `SrgbImGuiExtensions` (`Srgb` → `ImColor`/`ImGuiVector4`/`ImU32`, packed directly with no linear round-trip). The `ImColor` and `Srgb` `ToImGuiU32` apply the global style alpha like `ImGui.GetColorU32`; the linear `Color.ToImGuiU32` is a pure pack matching `ColorConvertFloat4ToU32`. `ImColor` extension operations: adjustments (lighten/darken, saturate/desaturate, hue offset, grayscale, invert, alpha), analysis (relative luminance, contrast ratio, perceptual distance), and contrast heuristics (`MostReadableTextColor`, `AdjustForSufficientContrast`). All color math delegates to `ktsu.Semantics.Color`. (There is no `ImColor` factory class — construct via `Color`/`Srgb` and convert.)
- **ImGui.Styler** (`ktsu.ImGui.Styler`) - Theming system with 50+ built-in themes, scoped styling, Button.Alignment, Text.Color semantic colors, Indent utilities, Alignment helpers, theme-aware color palette (`Palette`, e.g. `Palette.Basic.Red`, `Palette.Semantic.Error`), and interactive theme browser. Color construction and manipulation live in `ImGui.Color`.
Expand Down
37 changes: 37 additions & 0 deletions ImGui.Widgets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ The widgets below are grouped by what they are for. Everything is a static metho
- **`TabPanel`**: Tabbed interface with closable, reorderable tabs and dirty indicators
- **`Card`**: A scoped elevated panel that draws its shadow and rounded background behind whatever the `using` block renders
- **`Tree`**: Collapsible `Branch` and terminal `Leaf` nodes, with connector lines drawn between them
- **`ReorderableTree`**: Rows the caller draws, laid out as a tree and dragged to reorder them or move them in and out of containers. Reports the move rather than applying it. See [Reorderable Tree](#reorderable-tree) below
- **`ImageCanvas`**: A pannable, zoomable image canvas with a checkerboard backing for transparency
- **`AssetBrowser`**: A virtualized grid of thumbnail tiles over a caller-owned `AssetBrowserState`, with click, Ctrl+click and Shift+click selection, arrow-key focus, Ctrl+wheel tile resizing, double-click or Enter to activate, and dragging the selection out as a payload a drop target reads with `TryAcceptAssetPayload`. Only the visible rows ask for a label or thumbnail, so fifty thousand assets cost a screenful
- **`Toolbar`** / **`ToolbarButton`** / **`ToolbarToggleButton`** / **`ToolbarSeparator`**: A horizontal strip of buttons, each holding a glyph and a label inside one frame — glyph leading, glyph above with the label centred beneath, glyph only (the label becomes the tooltip) or label only. See [Toolbar](#toolbar) below
Expand Down Expand Up @@ -585,6 +586,42 @@ using (var tree = new ImGuiWidgets.Tree())
}
```

### Reorderable Tree

A tree whose rows can be dragged. The caller flattens its own tree into rows, top first, leaving out
the children of anything collapsed, and draws each row's content itself; the widget lays the rows
out, draws where a drag would land, and reports the move on release. It never applies it, so the move
goes through the caller's own model and undo history:

```csharp
ReorderableTreeRow[] rows = [new(0, false), new(0, true), new(1, false), new(1, false)];

if (ImGuiWidgets.ReorderableTree("layers", rows, i => ImGui.Selectable(names[i])) is ReorderableTreeMove move)
{
// move.Row moves under move.ParentRow (-1 for the top level), into the gap move.Index.
}
```

The top quarter of a container row drops above it, the bottom quarter below it, and the middle half
inside it. A leaf splits at the middle. Below the last row of a subtree, how far left the pointer is
picks how far out the drop lands. A drop onto the dragged row or anywhere in its subtree is refused,
and so is one that would leave the row where it is.

`Index` counts the new parent's children **before the move**, which is the gap the insertion line was
drawn in. When a row moves down within its own parent, its old place is among those children, so a
caller whose insert takes the final position subtracts one in exactly that case:

```csharp
if (sameParent && oldIndex < move.Index)
{
index = move.Index - 1;
}
```

A drag starts from a press anywhere on a row once the mouse moves past ImGui's drag threshold, so a
click that does not move is left to the row's own items. Each row is marked for probes as
`row{index}` inside the tree's label.

### Scoped Id

The scoped ID utility class helps in creating scoped IDs for ImGui elements and ensuring they get popped appropriately:
Expand Down
Loading
Loading