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
9 changes: 9 additions & 0 deletions docs/_pipeline/apps/styling/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ public override Element Render()
}
// </snippet:lightweight-styling>

// <snippet:font-family-resource>
class FontFamilyResourceExample : Component
{
public override Element Render() =>
Button("Code").Resources(r => r
.Set("ContentControlThemeFontFamily", new Microsoft.UI.Xaml.Media.FontFamily("Consolas")));
}
// </snippet:font-family-resource>

// <snippet:custom-resource>
class CustomResourceExample : Component
{
Expand Down
22 changes: 19 additions & 3 deletions docs/_pipeline/templates/styling.md.dt
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,28 @@ pressed, disabled) automatically respect your overrides:

![Lightweight styling](screenshot://styling/lightweight-styling)

`ResourceBuilder` supports `.Set(key, ThemeRef)` for theme-reactive
overrides, `.Set(key, string)` for hex colors, `.Set(key, double)` for
numeric values, and `.Set(key, CornerRadius)` for corner radius.
`ResourceBuilder.Set` has explicit overloads for `ThemeRef`, `Brush`,
color strings, `double`, `CornerRadius`, `Thickness`, `FontWeight`,
`FontFamily`, `bool`, and `GridLength`. Use `Thickness` for padding
or border thickness, and `FontWeight` / `FontFamily` for typography resources.
Comment thread
azchohfi marked this conversation as resolved.
Literal values retain their types; `ThemeRef` overrides re-resolve on theme changes.

`ResourceBuilder.SetColor(key, color)` stores a literal `Color`, not a brush.
Use it only for Color-typed resources; brush-typed keys such as `ButtonBackground`
need the string or `Brush` overload of `Set`. The string overload
still parses a hex or named color into a brush; it does not store arbitrary
text. Integer literals continue to select the `double` overload, so pass the
value type expected by the WinUI resource key rather than relying on conversion.
Overrides cascade to child elements through WinUI's resource dictionary
hierarchy.

Font-family resource keys require a `FontFamily` object, not a string:

```csharp snippet="styling/font-family-resource"
```

Calling `Set(key, "Consolas")` would select the color-string overload instead.

## Custom resource access

For brushes Reactor hasn't surfaced as a typed property, `Theme.Ref()`
Expand Down
28 changes: 25 additions & 3 deletions docs/guide/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,34 @@ class LightweightStylingExample : Component

![Lightweight styling](images/styling/lightweight-styling.png)

`ResourceBuilder` supports `.Set(key, ThemeRef)` for theme-reactive
overrides, `.Set(key, string)` for hex colors, `.Set(key, double)` for
numeric values, and `.Set(key, CornerRadius)` for corner radius.
`ResourceBuilder.Set` has explicit overloads for `ThemeRef`, `Brush`,
color strings, `double`, `CornerRadius`, `Thickness`, `FontWeight`,
`FontFamily`, `bool`, and `GridLength`. Use `Thickness` for padding
or border thickness, and `FontWeight` / `FontFamily` for typography resources.
Literal values retain their types; `ThemeRef` overrides re-resolve on theme changes.

`ResourceBuilder.SetColor(key, color)` stores a literal `Color`, not a brush.
Use it only for Color-typed resources; brush-typed keys such as `ButtonBackground`
need the string or `Brush` overload of `Set`. The string overload
still parses a hex or named color into a brush; it does not store arbitrary
text. Integer literals continue to select the `double` overload, so pass the
value type expected by the WinUI resource key rather than relying on conversion.
Overrides cascade to child elements through WinUI's resource dictionary
hierarchy.

Font-family resource keys require a `FontFamily` object, not a string:

```csharp
class FontFamilyResourceExample : Component
{
public override Element Render() =>
Button("Code").Resources(r => r
.Set("ContentControlThemeFontFamily", new Microsoft.UI.Xaml.Media.FontFamily("Consolas")));
}
```

Calling `Set(key, "Consolas")` would select the color-string overload instead.

## Custom resource access

For brushes Reactor hasn't surfaced as a typed property, `Theme.Ref()`
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/002-winui3-gap-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ by conditional rendering — elements not returned from render are never created
| WinUI Feature | Status | Notes |
|---|---|---|
| **ResourceDictionary (app-level)** | Passthrough | ReactorApp loads XamlControlsResources; no DSL for authoring app-level dictionaries |
| **ResourceDictionary (per-element)** | Exposed | `.Resources(r => r.Set(key, value))` writes into that element's own `FrameworkElement.Resources`; `ResourceBuilder.Set` overloads accept a hex/named color string, a `Brush`, a `ThemeRef`, a `double`, or a `CornerRadius` |
| **ResourceDictionary (per-element)** | Exposed | `.Resources(r => r.Set(key, value))` writes into that element's own `FrameworkElement.Resources`; `Set` accepts color strings, `Brush`, `ThemeRef`, `double`, `CornerRadius`, `Thickness`, `FontWeight`, `FontFamily`, `bool`, and `GridLength`. `SetColor` stores raw `Color` values, not brushes |
| **Resource lookup chain** | Passthrough | WinUI's chain works on underlying controls; a per-element override participates in it normally |
| **Merged dictionaries** | Passthrough | Can merge via `.Set()` on Application.Resources |
| **Theme dictionaries** | Passthrough | WinUI's theme dictionaries work natively; `ThemeRef` resolution reads through them |
Expand Down
9 changes: 9 additions & 0 deletions docs/specs/015-styling-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,15 @@ public class ResourceBuilder
return this;
}

public ResourceBuilder Set(string key, Thickness value) { _resources[key] = value; return this; }
public ResourceBuilder Set(string key, FontWeight value) { _resources[key] = value; return this; }
public ResourceBuilder Set(string key, FontFamily value) { _resources[key] = value; return this; }
public ResourceBuilder Set(string key, bool value) { _resources[key] = value; return this; }
public ResourceBuilder Set(string key, GridLength value) { _resources[key] = value; return this; }

// Literal Color resources only. Brush-typed resources use Set(string/Brush/ThemeRef).
public ResourceBuilder SetColor(string key, Color value) { _resources[key] = value; return this; }

internal ResourceOverrides Build() => new(_resources, _themeResources);
}

Expand Down
6 changes: 6 additions & 0 deletions docs/specs/tasks/styling-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ competitive advantage — no other C# declarative framework surfaces this.
- [x] `Set(string key, ThemeRef themeRef)` — stores for reactive resolution
- [x] `Set(string key, double value)` — for numeric resource keys
- [x] `Set(string key, CornerRadius value)` — for corner radius keys
- [x] `Set(string key, Thickness value)` — padding and border thickness
- [x] `Set(string key, FontWeight value)` — font weight
- [x] `Set(string key, FontFamily value)` — font family, compared by Source
- [x] `Set(string key, bool value)` — Boolean resources
- [x] `Set(string key, GridLength value)` — grid length resources
- [x] `SetColor(string key, Color value)` — raw Color only, not a Brush
- [x] `internal ResourceOverrides Build()` — returns immutable snapshot

### 4.2 — ResourceOverrides record
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4902,9 +4902,15 @@ ToString() → string
new()
Set(string key, Brush brush) → ResourceBuilder
Set(string key, CornerRadius value) → ResourceBuilder
Set(string key, FontFamily value) → ResourceBuilder
Set(string key, FontWeight value) → ResourceBuilder
Set(string key, GridLength value) → ResourceBuilder
Set(string key, ThemeRef themeRef) → ResourceBuilder
Set(string key, Thickness value) → ResourceBuilder
Set(string key, bool value) → ResourceBuilder
Set(string key, double value) → ResourceBuilder
Set(string key, string color) → ResourceBuilder
SetColor(string key, Color value) → ResourceBuilder

### record ResourceOptions
new(TimeSpan? StaleTime = null, TimeSpan? CacheTime = null, int RetryCount = 0, bool RefetchOnMount = true, string CacheKey = null, bool RefetchOnWindowFocus = false)
Expand Down
6 changes: 6 additions & 0 deletions skills/reactor.api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4902,9 +4902,15 @@ ToString() → string
new()
Set(string key, Brush brush) → ResourceBuilder
Set(string key, CornerRadius value) → ResourceBuilder
Set(string key, FontFamily value) → ResourceBuilder
Set(string key, FontWeight value) → ResourceBuilder
Set(string key, GridLength value) → ResourceBuilder
Set(string key, ThemeRef themeRef) → ResourceBuilder
Set(string key, Thickness value) → ResourceBuilder
Set(string key, bool value) → ResourceBuilder
Set(string key, double value) → ResourceBuilder
Set(string key, string color) → ResourceBuilder
SetColor(string key, Color value) → ResourceBuilder

### record ResourceOptions
new(TimeSpan? StaleTime = null, TimeSpan? CacheTime = null, int RetryCount = 0, bool RefetchOnMount = true, string CacheKey = null, bool RefetchOnWindowFocus = false)
Expand Down
6 changes: 4 additions & 2 deletions src/Reactor/Core/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1498,8 +1498,8 @@ internal static bool ResourceOverridesEqual(

// Literals: compare by VALUE. Brushes are re-parsed to fresh instances each render
// (e.g. .Set("X", "#0078D4")), so reference compare would false-decline the skip for
// an UNCHANGED literal-override cell — use BrushesEqual; other literal kinds (double,
// CornerRadius) are value types whose Equals is correct.
// an UNCHANGED literal-override cell. FontFamily also compares by value (Source),
// not WinRT wrapper identity. The remaining literal kinds are value types.
var la = a.Literals;
var lb = b.Literals;
if (la.Count != lb.Count) return false;
Expand All @@ -1526,6 +1526,8 @@ private static bool ResourceLiteralEqual(object? a, object? b)
if (ReferenceEquals(a, b)) return true;
if (a is null || b is null) return false;
if (a is Brush ba && b is Brush bb) return BrushesEqual(ba, bb);
if (a is Microsoft.UI.Xaml.Media.FontFamily fa && b is Microsoft.UI.Xaml.Media.FontFamily fb)
return FontFamiliesEqual(fa, fb);
return a.Equals(b);
}
}
Expand Down
51 changes: 49 additions & 2 deletions src/Reactor/Elements/ResourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using Windows.UI.Text;

namespace Microsoft.UI.Reactor.Elements;

/// <summary>
/// Immutable snapshot of per-control resource overrides built by <see cref="ResourceBuilder"/>.
/// Contains both literal values (colors, doubles, corner radii) and theme-reactive
/// Contains both literal resource values and theme-reactive
/// <see cref="ThemeRef"/> entries that are re-resolved on theme change.
/// </summary>
public sealed record ResourceOverrides(
Expand Down Expand Up @@ -64,7 +66,7 @@ public ResourceBuilder Set(string key, ThemeRef themeRef)
return this;
}

/// <summary>Sets a numeric resource override (e.g., <c>ButtonBorderThemeThickness</c>).</summary>
/// <summary>Sets a numeric resource override (e.g., <c>BreadcrumbBarItemThemeFontSize</c>).</summary>
public ResourceBuilder Set(string key, double value)
{
_literals[key] = value;
Expand All @@ -78,6 +80,51 @@ public ResourceBuilder Set(string key, CornerRadius value)
return this;
}

/// <summary>Sets a thickness resource override (e.g., <c>BreadcrumbBarChevronPadding</c>).</summary>
public ResourceBuilder Set(string key, Thickness value)
{
_literals[key] = value;
return this;
}

/// <summary>Sets a font weight resource override (e.g., <c>BreadcrumbBarItemFontWeight</c>).</summary>
public ResourceBuilder Set(string key, FontWeight value)
{
_literals[key] = value;
return this;
}

/// <summary>Sets a font family resource override (e.g., <c>ContentControlThemeFontFamily</c>).</summary>
public ResourceBuilder Set(string key, FontFamily value)
{
_literals[key] = value;
return this;
}
Comment thread
azchohfi marked this conversation as resolved.

/// <summary>Sets a Boolean resource override (e.g., <c>GridViewItemSelectionCheckMarkVisualEnabled</c>).</summary>
public ResourceBuilder Set(string key, bool value)
{
_literals[key] = value;
return this;
}

/// <summary>
/// Sets a literal color resource override, not a brush. Use the string or
/// <see cref="Brush"/> overload for resources that expect a brush.
/// </summary>
public ResourceBuilder SetColor(string key, Color value)
{
_literals[key] = value;
return this;
}
Comment thread
azchohfi marked this conversation as resolved.

/// <summary>Sets a grid length resource override (e.g., <c>ContentDialogButtonSpacing</c>).</summary>
public ResourceBuilder Set(string key, GridLength value)
{
_literals[key] = value;
return this;
}

/// <summary>Builds an immutable <see cref="ResourceOverrides"/> snapshot.</summary>
internal ResourceOverrides Build()
{
Expand Down
Loading
Loading