From 6b37a72733ac80ace4b18af78fdf6b02c794e360 Mon Sep 17 00:00:00 2001 From: SilkyFowl <16532218+SilkyFowl@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:11:17 +0900 Subject: [PATCH 01/18] update AvaloniaVersion to 11.3.8 --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5132d06f..afe47d4f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 11.1.0 + 11.3.8 1.5.2 From af2334436e293a06cc38dd4ce9524c2b8ddc7e60 Mon Sep 17 00:00:00 2001 From: SilkyFowl <16532218+SilkyFowl@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:51:39 +0900 Subject: [PATCH 02/18] [WIP] Add ResourceDictionary bindings. --- .../Avalonia.FuncUI.UnitTests.fsproj | 1 + .../DSL/Base/ResourceDictionaryTests.fs | 259 ++++++++++++++++++ src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj | 1 + .../DSL/Base/ResourceDictionary.fs | 52 ++++ 4 files changed, 313 insertions(+) create mode 100644 src/Avalonia.FuncUI.UnitTests/DSL/Base/ResourceDictionaryTests.fs create mode 100644 src/Avalonia.FuncUI/DSL/Base/ResourceDictionary.fs diff --git a/src/Avalonia.FuncUI.UnitTests/Avalonia.FuncUI.UnitTests.fsproj b/src/Avalonia.FuncUI.UnitTests/Avalonia.FuncUI.UnitTests.fsproj index 2e550c8e..c63e31b0 100644 --- a/src/Avalonia.FuncUI.UnitTests/Avalonia.FuncUI.UnitTests.fsproj +++ b/src/Avalonia.FuncUI.UnitTests/Avalonia.FuncUI.UnitTests.fsproj @@ -16,6 +16,7 @@ + diff --git a/src/Avalonia.FuncUI.UnitTests/DSL/Base/ResourceDictionaryTests.fs b/src/Avalonia.FuncUI.UnitTests/DSL/Base/ResourceDictionaryTests.fs new file mode 100644 index 00000000..b1cc319d --- /dev/null +++ b/src/Avalonia.FuncUI.UnitTests/DSL/Base/ResourceDictionaryTests.fs @@ -0,0 +1,259 @@ +namespace Avalonia.FuncUI.UnitTests.DSL + +open Avalonia +open Avalonia.Controls +open global.Xunit + +module ResourceDictionaryTests = + open Avalonia.FuncUI.VirtualDom + open Avalonia.FuncUI.DSL + open Avalonia.FuncUI.Types + open Avalonia.FuncUI.VirtualDom + + module private VirtualDom = + open Avalonia.FuncUI.Builder + + let create<'t when 't :> AvaloniaObject> (view: IView<'t>) : 't = VirtualDom.createObject view :?> 't + + let update<'t when 't :> AvaloniaObject> (target: 't) (last: IView<'t>) (next: IView<'t>) : unit = + let diff = Differ.diff (last, next) + Patcher.patch (target, diff) + + module private Assert = + let containsKey (dict: ResourceDictionary) (key: obj) = + let isFound, value = dict.TryGetResource(key, null) + Assert.True(isFound, $"ResourceDictionary does not contain expected key: {key}.") + value + + let notContainsKey (dict: ResourceDictionary) (key: obj) = + let isFound, value = dict.TryGetResource(key, null) + Assert.False(isFound, $"ResourceDictionary unexpectedly contains key: {key}. Found value: {value}") + + let containsKeyWithValueWith (dict: ResourceDictionary) (key: 'k) (expectedValue: 'v) (converter: obj -> 'v) = + let isFound, actualValue = dict.TryGetResource(key, null) + let expectedValue = box expectedValue + + Assert.True(isFound, $"ResourceDictionary does not contain expected key: {key}.") + Assert.Equal(expectedValue, converter actualValue) + + let containsKeyWithValue (dict: ResourceDictionary) (key: 'k) (expectedValue: 'v) = + containsKeyWithValueWith dict key expectedValue (fun o -> o :?> 'v) + + module ``keyValue`` = + + + [] + let ``can set`` () = + let initView = ResourceDictionary.create [] + let target = VirtualDom.create initView + + let key = "myKey" + let value = "myValue" + + Assert.notContainsKey target key + + let updatedView = + ResourceDictionary.create [ ResourceDictionary.keyValue (key, value) ] + + VirtualDom.update target initView updatedView + + Assert.containsKeyWithValue target key value + + let revertedView = ResourceDictionary.create [] + VirtualDom.update target updatedView revertedView + + Assert.notContainsKey target key + + [] + let ``can set null value`` = + let initView = ResourceDictionary.create [] + let target = VirtualDom.create initView + + let key = "myKey" + let value = null + Assert.notContainsKey target key + + let updatedView = + ResourceDictionary.create [ ResourceDictionary.keyValue (key, value = value) ] + + VirtualDom.update target initView updatedView + Assert.containsKeyWithValue target key value + + VirtualDom.update target updatedView initView + Assert.notContainsKey target key + + + [] + let ``can update value`` () = + let key = "myKey" + + let createView (value: obj) = + ResourceDictionary.create [ ResourceDictionary.keyValue (key, value = value) ] + + let createViewWithIView value = + ResourceDictionary.create [ ResourceDictionary.keyValue (key, view = value) ] + + let initialValue = "initialValue" + let initView = createView initialValue + let target = VirtualDom.create initView + + Assert.containsKeyWithValue target key initialValue + + List.fold + (fun lastView acc -> + match acc with + | Choice1Of2(nextValue: obj) -> + let updatedView = createView nextValue + VirtualDom.update target lastView updatedView + Assert.containsKeyWithValue target key nextValue + updatedView + | Choice2Of2(nextValue, expected, converter) -> + let updatedView = createViewWithIView nextValue + VirtualDom.update target lastView updatedView + Assert.containsKeyWithValueWith target key expected converter + updatedView) + initView + [ Choice1Of2 42 + Choice1Of2 "a string" + Choice1Of2 null + Choice1Of2 3.14 + Choice2Of2(TextBlock.create [ TextBlock.text "Hello" ], "Hello", fun o -> (o :?> TextBlock).Text) + Choice1Of2 true + Choice1Of2(TextBox(Text = "Hello, World!")) + Choice1Of2(ContentControl(Content = Button(Content = "Click Me"))) ] + + + [] + let ``can update key`` () = + let initialKey = "initialKey" + let updatedKey = "updatedKey" + let value = "myValue" + + let initView = + ResourceDictionary.create [ ResourceDictionary.keyValue (initialKey, value) ] + + let target = VirtualDom.create initView + + Assert.containsKeyWithValue target initialKey value + + let updatedView = + ResourceDictionary.create [ ResourceDictionary.keyValue (updatedKey, value) ] + + VirtualDom.update target initView updatedView + + Assert.notContainsKey target initialKey + Assert.containsKeyWithValue target updatedKey value + + + [] + let ``can set multiple`` () = + let key1 = "key1" + let value1 = "value1" + let updatedValue1 = "updatedValue1" + let key2 = typeof