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
14 changes: 5 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,19 @@ This is a .NET library (`ktsu.ThemeProvider`) providing a semantic color theming
- `ThemeProvider/ISemanticTheme.cs` - Core theme interface (SemanticMapping + IsDarkTheme)
- `ThemeProvider/SemanticColorMapper.cs` - Maps semantic color requests to actual colors using Oklab color space
- `ThemeProvider/ThemeRegistry.cs` - Central registration of all 44 themes with metadata and factory functions
- `ThemeProvider/ColorMath.cs` - Color space conversions (RGB/Oklab), WCAG accessibility, and gradient generation
- `ThemeProvider/PerceptualColor.cs` - Color representation with Oklab perceptual properties
- `ThemeProvider/RgbColor.cs` - Linear RGB color with hex/byte conversions
- `ThemeProvider/SRgbColor.cs` - sRGB gamma-corrected color with linear conversion
- `ThemeProvider/OklabColor.cs` - Oklab perceptual color space with polar (LCh) support
- Color types (`Color`, `Srgb`, `Oklab`/`Oklch`, `AccessibilityLevel`, color-space conversions, WCAG, gradients) come from the **`ktsu.Semantics.Color`** package — there are no in-house color types (they were removed in v2.0)
- `ThemeProvider/IPaletteMapper.cs` - Generic interface for framework-specific color mapping
- `ThemeProvider/SemanticColorRequest.cs` - Readonly record struct combining SemanticMeaning + Priority
- `ThemeProvider/SemanticMeaning.cs` - Enum of semantic color purposes (Neutral, Primary, Error, etc.)
- `ThemeProvider/Priority.cs` - Enum of 7 priority levels (VeryLow to VeryHigh)
- `ThemeProvider/ColorRange.cs` - Color range interpolation helper
- `ThemeProvider/AccessibilityLevel.cs` - WCAG accessibility levels (Fail, AA, AAA)
- `ThemeProvider/ColorRange.cs` - Color range interpolation helper (built on `ktsu.Semantics.Color`)
- `ThemeProvider/Themes/` - 44 theme implementations organized by family
- `ThemeProvider.ImGui/ImGuiPaletteMapper.cs` - Dear ImGui integration mapping ImGuiCol to Vector4
- `ThemeProviderDemo/Program.cs` - Interactive demo application using ktsu.ImGuiApp

### Dependencies

- **ktsu.Semantics.Color** - Color value types and color science (`Color`, Oklab/Oklch/HSL/HSV, WCAG, gradients)
- **Polyfill** - Backfill support for newer .NET APIs on older targets
- **System.Numerics.Vectors** - Vector types for netstandard2.0 target
- **Hexa.NET.ImGui** - Dear ImGui bindings (ThemeProvider.ImGui project)
Expand All @@ -59,7 +55,7 @@ This is a .NET library (`ktsu.ThemeProvider`) providing a semantic color theming

The library uses meaning-based color specifications instead of hardcoded colors:

1. **ISemanticTheme** provides a `Dictionary<SemanticMeaning, Collection<PerceptualColor>>` mapping
1. **ISemanticTheme** provides a `Dictionary<SemanticMeaning, Collection<Color>>` mapping (`Color` from `ktsu.Semantics.Color`)
2. **SemanticColorMapper** maps `SemanticColorRequest` (meaning + priority) to actual colors
3. The mapper calculates a global lightness range across all theme colors, then interpolates/extrapolates in Oklab space to achieve target lightness for each priority level
4. Dark themes: higher priority = higher lightness; Light themes: higher priority = lower lightness
Expand All @@ -68,7 +64,7 @@ The library uses meaning-based color specifications instead of hardcoded colors:
### Adding New Themes

1. Create a new class in `ThemeProvider/Themes/{Family}/{ThemeName}.cs`
2. Implement `ISemanticTheme` with static `PerceptualColor` fields from hex values using `PerceptualColor.FromRgb("#hex")`
2. Implement `ISemanticTheme` with static `Color` fields from hex values using `Color.FromHex("#hex")` (`Color` from `ktsu.Semantics.Color`)
3. Map semantic meanings to color collections (Neutral typically gets multiple colors; other meanings get single accent colors)
4. Register in `ThemeRegistry.AllThemes` with a `ThemeInfo` record

Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="ktsu.Semantics.Color" Version="2.3.0" />
<PackageVersion Include="Hexa.NET.ImGui" Version="2.2.9" />
<PackageVersion Include="ktsu.ImGui.App" Version="2.15.3" />
<PackageVersion Include="Polyfill" Version="10.11.0" />
Expand Down
102 changes: 36 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ var requests = new[]
new SemanticColorRequest(SemanticMeaning.Error, Priority.High),
new SemanticColorRequest(SemanticMeaning.Neutral, Priority.VeryLow),
};
IReadOnlyDictionary<SemanticColorRequest, PerceptualColor> colors =
IReadOnlyDictionary<SemanticColorRequest, Color> colors =
SemanticColorMapper.MapColors(requests, theme);
```

Expand Down Expand Up @@ -106,17 +106,17 @@ IReadOnlyList<ISemanticTheme> gruvboxInstances = CreateThemeInstancesInFamily("G

```csharp
using ktsu.ThemeProvider;
using ktsu.Semantics.Color;

var theme = new Themes.Nord.Nord();

// Generate the complete palette (all meaning + priority combinations)
IReadOnlyDictionary<SemanticColorRequest, PerceptualColor> completePalette =
IReadOnlyDictionary<SemanticColorRequest, Color> completePalette =
SemanticColorMapper.MakeCompletePalette(theme);

// Access any color from the palette
var primaryMedium = completePalette[new SemanticColorRequest(SemanticMeaning.Primary, Priority.Medium)];
RgbColor rgb = primaryMedium.RgbValue;
string hex = rgb.ToHex();
string hex = primaryMedium.ToHex();
```

### Dear ImGui Integration
Expand Down Expand Up @@ -145,21 +145,22 @@ foreach ((ImGuiCol colorKey, Vector4 colorValue) in imguiColors)

```csharp
using ktsu.ThemeProvider;
using ktsu.Semantics.Color;

var foreground = RgbColor.FromHex("#FFFFFF");
var background = RgbColor.FromHex("#1E1E2E");
Color foreground = Color.FromHex("#FFFFFF");
Color background = Color.FromHex("#1E1E2E");

// Calculate contrast ratio
float contrastRatio = ColorMath.GetContrastRatio(foreground, background);
// Calculate contrast ratio (1.0 .. 21.0)
double contrastRatio = foreground.ContrastRatio(background);

// Check WCAG compliance
AccessibilityLevel level = ColorMath.GetAccessibilityLevel(foreground, background, isLargeText: false);
AccessibilityLevel level = foreground.AccessibilityLevelAgainst(background, largeText: false);

// Adjust a color to meet accessibility requirements
RgbColor adjusted = ColorMath.AdjustForAccessibility(foreground, background, AccessibilityLevel.AA);
Color adjusted = foreground.AdjustForContrast(background, AccessibilityLevel.AA);

// Create perceptually uniform gradients
RgbColor[] gradient = ColorMath.CreateGradient(foreground, background, steps: 10);
IReadOnlyList<Color> gradient = foreground.Gradient(background, steps: 10);
```

## Advanced Usage
Expand Down Expand Up @@ -205,19 +206,20 @@ Implement `ISemanticTheme` with colors from your palette:

```csharp
using ktsu.ThemeProvider;
using ktsu.Semantics.Color;
using System.Collections.ObjectModel;

public class MyCustomTheme : ISemanticTheme
{
private static readonly PerceptualColor Background = PerceptualColor.FromRgb("#1A1B26");
private static readonly PerceptualColor Foreground = PerceptualColor.FromRgb("#C0CAF5");
private static readonly PerceptualColor Blue = PerceptualColor.FromRgb("#7AA2F7");
private static readonly PerceptualColor Green = PerceptualColor.FromRgb("#9ECE6A");
private static readonly PerceptualColor Red = PerceptualColor.FromRgb("#F7768E");
private static readonly Color Background = Color.FromHex("#1A1B26");
private static readonly Color Foreground = Color.FromHex("#C0CAF5");
private static readonly Color Blue = Color.FromHex("#7AA2F7");
private static readonly Color Green = Color.FromHex("#9ECE6A");
private static readonly Color Red = Color.FromHex("#F7768E");

public bool IsDarkTheme => true;

public Dictionary<SemanticMeaning, Collection<PerceptualColor>> SemanticMapping { get; } = new()
public Dictionary<SemanticMeaning, Collection<Color>> SemanticMapping { get; } = new()
{
{ SemanticMeaning.Neutral, new() { Background, Foreground } },
{ SemanticMeaning.Primary, new() { Blue } },
Expand Down Expand Up @@ -278,8 +280,8 @@ Static class that maps semantic color requests to actual colors.

| Name | Return Type | Description |
|------|-------------|-------------|
| `MapColors(requests, theme)` | `IReadOnlyDictionary<SemanticColorRequest, PerceptualColor>` | Maps a collection of requests to colors using the theme |
| `MakeCompletePalette(theme)` | `IReadOnlyDictionary<SemanticColorRequest, PerceptualColor>` | Generates all possible meaning+priority combinations for a theme |
| `MapColors(requests, theme)` | `IReadOnlyDictionary<SemanticColorRequest, Color>` | Maps a collection of requests to colors using the theme |
| `MakeCompletePalette(theme)` | `IReadOnlyDictionary<SemanticColorRequest, Color>` | Generates all possible meaning+priority combinations for a theme |

### `ThemeRegistry`

Expand All @@ -304,57 +306,25 @@ Static class providing centralized theme discovery and management.
| `CreateAllThemeInstances()` | `IReadOnlyList<ISemanticTheme>` | Creates instances of all themes |
| `CreateThemeInstancesInFamily(family)` | `IReadOnlyList<ISemanticTheme>` | Creates instances of themes in a family |

### `ColorMath`
### Color types (`ktsu.Semantics.Color`)

Static class providing color space conversions and accessibility utilities.
Colors are represented by the `Color` type from the [`ktsu.Semantics.Color`](https://www.nuget.org/packages/ktsu.Semantics.Color) package (linear RGB + alpha, gamma-correct). It is the currency type for themes (`ISemanticTheme.SemanticMapping`) and `SemanticColorMapper`.

#### Methods

| Name | Return Type | Description |
|------|-------------|-------------|
| `RgbToOklab(rgb)` | `OklabColor` | Converts linear RGB to Oklab color space |
| `OklabToRgb(oklab)` | `RgbColor` | Converts Oklab to linear RGB color space |
| `GetRelativeLuminance(rgb)` | `float` | Calculates WCAG relative luminance |
| `GetContrastRatio(color1, color2)` | `float` | Calculates WCAG contrast ratio (1:1 to 21:1) |
| `GetAccessibilityLevel(fg, bg, isLargeText)` | `AccessibilityLevel` | Checks WCAG AA/AAA compliance |
| `AdjustForAccessibility(fg, bg, level, isLargeText)` | `RgbColor` | Adjusts color to meet WCAG requirements |
| `CreateGradient(from, to, steps)` | `RgbColor[]` | Creates a perceptually uniform gradient |

### `PerceptualColor`
Common members:

Readonly record struct representing a color with perceptual properties in Oklab space.
| Member | Description |
|--------|-------------|
| `Color.FromHex(hex)` | Creates a color from an sRGB hex string (proper sRGB→linear decode) |
| `ToHex()` / `ToBytes()` | Converts back to an sRGB hex string / 8-bit channels |
| `ToSrgbVector4()` | sRGB-encoded `Vector4` for UI frameworks (e.g. ImGui) |
| `ToOklab()` / `ToOklch()` | Perceptual (Oklab / polar LCh) representations |
| `ContrastRatio(other)` | WCAG contrast ratio (1:1 .. 21:1) |
| `AccessibilityLevelAgainst(bg, largeText)` | WCAG AA/AAA compliance (returns `AccessibilityLevel`) |
| `AdjustForContrast(bg, level, largeText)` | Adjusts lightness to meet a WCAG level |
| `DistanceTo(other)` | Perceptual (Oklab) distance |
| `MixOklab(other, t)` / `Gradient(to, steps)` | Perceptual blend / uniform gradient |

#### Properties

| Name | Type | Description |
|------|------|-------------|
| `OklabValue` | `OklabColor` | The color in Oklab perceptual space |
| `RgbValue` | `RgbColor` | The RGB representation |
| `Hue` | `float` | Hue in Oklab polar coordinates |
| `Chroma` | `float` | Chroma (colorfulness) in Oklab polar coordinates |
| `Lightness` | `float` | Lightness in Oklab space |

#### Methods

| Name | Return Type | Description |
|------|-------------|-------------|
| `FromRgb(RgbColor)` | `PerceptualColor` | Creates from an RGB color |
| `FromRgb(string hex)` | `PerceptualColor` | Creates from a hex color string |
| `SemanticDistanceTo(other)` | `float` | Calculates perceptual distance to another color |

### `RgbColor`

Readonly record struct representing a linear RGB color with float precision.

#### Methods

| Name | Return Type | Description |
|------|-------------|-------------|
| `FromBytes(r, g, b)` | `RgbColor` | Creates from 8-bit values (0-255) |
| `FromHex(hex)` | `RgbColor` | Creates from hex string (e.g., "#FF0000") |
| `ToHex()` | `string` | Converts to hex string |
| `ToBytes()` | `(byte R, byte G, byte B)` | Converts to 8-bit values |
| `ToSRgb()` | `SRgbColor` | Converts to sRGB gamma-corrected values |
> **Migration note (v2.0):** ThemeProvider's in-house `RgbColor`, `SRgbColor`, `OklabColor`, `PerceptualColor`, and `ColorMath` types were removed in favour of `ktsu.Semantics.Color`. This also fixed a long-standing sRGB-as-linear gamma bug — base theme colors render identically, but the semantic mapper's derived colors and accessibility numbers are now computed correctly.

### `IPaletteMapper<TColorKey, TColorValue>`

Expand Down
Loading
Loading