Skip to content

Conversation

@SilkyFowl
Copy link
Contributor

@SilkyFowl SilkyFowl commented Nov 15, 2025

This PR updates Avalonia from v11.1.0 to v11.3.9. With this version update, ResourceDictionary now inherits from AvaloniaObject, enabling us to implement ResourceDictionary-related DSL bindings.

Changes

Avalonia Version Update

  • Updated Avalonia from v11.1.0 to v11.3.9 (Directory.Build.props)

ResourceDictionary DSL Implementation

New file: ResourceDictionary.fs

  • ResourceDictionary.keyValue: Supports setting key-value pairs
    • Provides overloads for both object values (obj) and IView values
    • Implements clear functionality using ClearValue type
  • ResourceDictionary.mergedDictionaries: Supports merged dictionaries
  • ResourceDictionary.themeDictionariesKeyValue: Supports theme variant-specific resource dictionaries

ResourceInclude Wrapper Implementation

Since ResourceInclude does not inherit from AvaloniaObject, we implemented a ResourceIncludeWrapper class:

  • Wraps ResourceInclude with a class that inherits from ResourceProvider
  • Recreates the internal instance when the Source property changes to ensure resource updates
  • Provides helper functions: fromUri, fromString

ResourceProvider DSL Implementation

New file: ResourceProvider.fs

  • onResourceObservable: Binding that subscribes to GetResourceObservable
    • Supports optional defaultThemeVariant parameter
    • Wraps results in option type (treating null and UnsetValue as None)

StyledElement DSL Extensions

StyledElement.fs

  • StyledElement.resources: Added overload that accepts IView
  • StyledElement.onResourceObservable: Resource observation binding for StyledElement

Test Infrastructure Improvements

  • Introduced Avalonia.Headless.XUnit package: Required for testing ResourceInclude
  • Added ResourceDictionaryTests.fs: Comprehensive test suite (493 lines)
  • Added TestHelpers.fs: Assertion helpers for tests
  • Updated existing tests: Significantly expanded resource-related tests in StyledElementTests.fs
  • Added test resource files: TestResources1.xaml, TestResources2.xaml

Minor Fixes

  • AttrBuilder.fs: Fixed typos in documentation comments
  • Types.fs, VirtualDom.Delta.fs, VirtualDom.Patcher.fs: Fixed typos in documentation comments

Implementation Details and Future Improvements

Dictionary-Type Property Implementation

Dictionary-type properties such as ResourceDictionary.keyValue and themeDictionariesKeyValue are currently implemented using AttrBuilder<'t>.CreateContentSingle. While this approach is unconventional, it has the beneficial side effect of allowing Views and raw values to be seamlessly mixed.

Current Implementation (Works)

StyledElement.create [
  StyledElement.resources (
    ResourceDictionary.create [
      ResourceDictionary.keyValue ("key1", "value1")
      ResourceDictionary.keyValue ("key2", 42)
      ResourceDictionary.keyValue ("key3", TextBlock.create [ TextBlock.text "text" ])
    ]
  )
]

Potential Future Improvement

If we were to provide a more explicit and type-safe API, it might look like this:

type KeyValueItem =
  | KeyValue of key:obj * valueObj:obj
  | KeyView of key:obj * valueView:IView

[<AbstractClass; Sealed>]
type AttrBuilder<'view> =
    new unit -> AttrBuilder<'view>
    static member CreateDictionaryProperty:
      name: string *
      getter: ('view -> obj) voption *
      setter: ('view * obj -> unit) voption *
      dictionaryProperty: KeyValueItem list
              -> IAttr<'view>

    static member CreateContentDictionary:
        name: string *
        getter: ('view -> obj) voption *
        setter: ('view * obj -> unit) voption *
        dictionaryContent: KeyValueItem list
            -> IAttr<'view>

Usage example with this API:

StyledElement.create [
  StyledElement.resources (
    ResourceDictionary.create [
      ResourceDictionary.keyValues [
        KeyValue("key1", "value1")
        KeyValue("key2", 42)
        KeyView("key3", TextBlock.create [ TextBlock.text "text" ])
      ]
    ]
  )
]

However, this change would require a significant update to the DSL API. If we decide to pursue this approach, it should be handled in a separate issue and pull request.

* Extend subscription system from Control to AvaloniaObject for broader compatibility
* Improve code formatting and documentation with XML comments

Breaking Changes:
- Avalonia.FuncUI.Types.Subscription.Subscribe  now accepts AvaloniaObject instead of Control
* Support both Uri and string-based factory methods (fromUri, fromString)

Technical Details:
- ResourceInclude doesn't inherit AvaloniaObject, solved with wrapper pattern
- ResourceInclude cannot update internal state, handled by instance recreation
- Added Headless test infrastructure for proper UI thread execution
…ructure

- Add StyledElement.resources IView overload for DSL-style resource management
- Create TestHelpers.fs with reusable Headless, VirtualDom, and Assert modules
- Reorganize tests by functionality with improved module structure
Migrated from manual session management with custom code using only Avalonia.Headless
to framework-managed approach with Avalonia.Headless.XUnit, resolving "Call from invalid thread"
errors and test hanging issues.

- Add Avalonia.Headless.XUnit package reference
- Change UI thread accessing tests to use [AvaloniaFact] attribute
- Remove unnecessary manual session management code

Note: Avalonia.Headless was initially introduced in commit 3c67231
to implement tests containing XAML code.
@JaggerJo JaggerJo requested a review from Numpsy November 15, 2025 14:42
@JaggerJo
Copy link
Member

@SilkyFowl wow, nice work!

Would be interesting to see how / what you use this for in your apps. Mainly for defining theme resources in F# I assume?

@SilkyFowl
Copy link
Contributor Author

SilkyFowl commented Nov 16, 2025

@JaggerJo Thank you for the review!

Yes, the primary use case is defining theme resources in F# without XAML. I plan to add a sample project to the Examples folder demonstrating these features.

Long-term Vision

Long-term, I want to achieve DSL-ification of the App class. To accomplish this, I recognize that we need to solve the #86 issue.

Challenges and Potential Solutions

  • The Style class inherits from AvaloniaObject, which is good
  • However, classes like Setter that don't inherit from AvaloniaObject will be obstacles
  • Since IView's constraint has been relaxed from Control to AvaloniaObject, we might be able to handle this by creating wrapper classes (similar to ResourceIncludeWrapper) for types like StyleIncludeWrapper

I'll create a separate issue to discuss these long-term goals in more detail.

@Numpsy
Copy link
Collaborator

Numpsy commented Nov 16, 2025

Sounds interesting, I'll have a look at the changes tommorow

- Add ColorPicker DSL
- Add themeVariantScopeView
…features

- Refactor ColorSet to use SolidColorBrush directly
- Replace GetResourceObservable().ToBinding() with DynamicResourceExtension
- Add debugOverlaysView for renderer diagnostics (DEBUG only)
- Simplify .editorconfig and fix formatting across DSL files
- Restrict DiagnosticsSupport package to DEBUG builds only
…mponent development

- Add FuncCommand helper for ICommand implementation (DEBUG only)
- Change MainWindow from HostWindow to Window with self-reference (as this)
- Add F12 key binding to open InspectorWindow
- Add Avalonia.FuncUI.Diagnostics project reference (DEBUG only)
- Prevent multiple inspector windows from opening
@SilkyFowl SilkyFowl changed the title Update to Avalonia v11.3.8 and Implement ResourceDictionary DSL Update to Avalonia v11.3.9 and Implement ResourceDictionary DSL Nov 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants