diff --git a/Directory.Build.props b/Directory.Build.props index 2df83a4a4..b07aac7a8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,13 @@ - 0.58.0 + 0.60.0 Fabulous Contributors - 0.58.0 - [All] Proper version constraints for the NuGet packages + 0.60.0-preview1 + [All] Proper version constraints for the NuGet packages (https://github.com/fsprojects/Fabulous/pull/797) +[All] Add FSharp.Core as a public dependency (https://github.com/fsprojects/Fabulous/pull/796) +[All] Reduced allocations (https://github.com/fsprojects/Fabulous/pull/805) +[Fabulous.XamarinForms] [Templates] Add native main menu to MacOS template (https://github.com/fsprojects/Fabulous/pull/806) False Apache-2.0 https://github.com/fsprojects/Fabulous diff --git a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/CodeGenerator.fs b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/CodeGenerator.fs index 3717f08f7..d4bfc5ff1 100644 --- a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/CodeGenerator.fs +++ b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/CodeGenerator.fs @@ -98,6 +98,63 @@ module CodeGenerator = w.printfn "" w + + let generateUpdateAttachedPropertiesFunction (data: UpdateAttachedPropertiesData) (w: StringWriter) = + let printUpdateBaseIfNeeded (space: string) (isUnitRequired: bool) = + match data.BaseName with + | None when isUnitRequired = false -> + () + | None -> + w.printfn "%s()" space + | Some baseName -> + w.printfn "%sViewBuilders.Update%sAttachedProperties(propertyKey, prevOpt, curr, target)" space baseName + + w.printfn " static member Update%sAttachedProperties (propertyKey: int, prevOpt: ViewElement voption, curr: ViewElement, target: obj) = " data.Name + if data.PropertiesWithAttachedProperties.Length = 0 then + printUpdateBaseIfNeeded " " true + else + w.printfn " match propertyKey with" + for p in data.PropertiesWithAttachedProperties do + w.printfn " | key when key = %s.KeyValue ->" (getAttributeKey p.CustomAttributeKey p.UniqueName) + + for ap in p.AttachedProperties do + let hasApply = not (System.String.IsNullOrWhiteSpace(ap.ConvertModelToValue)) || not (System.String.IsNullOrWhiteSpace(ap.UpdateCode)) + let attributeKey = getAttributeKey ap.CustomAttributeKey ap.UniqueName + + w.printfn " let prev%sOpt = match prevOpt with ValueNone -> ValueNone | ValueSome prevChild -> prevChild.TryGetAttributeKeyed<%s>(%s)" ap.UniqueName ap.ModelType attributeKey + w.printfn " let curr%sOpt = curr.TryGetAttributeKeyed<%s>(%s)" ap.UniqueName ap.ModelType attributeKey + w.printfn " let target = target :?> %s" (Option.defaultValue "MISSING_COLLECTION_ELEMENT_TYPE" p.CollectionDataElementType) + + if ap.ModelType = "ViewElement" && not hasApply then + w.printfn " match struct (prev%sOpt, curr%sOpt) with" ap.UniqueName ap.UniqueName + w.printfn " // For structured objects, dependsOn on reference equality" + w.printfn " | struct (ValueSome prevValue, ValueSome newValue) when identical prevValue newValue -> ()" + w.printfn " | struct (ValueSome prevValue, ValueSome newValue) when canReuseView prevValue newValue ->" + w.printfn " newValue.UpdateIncremental(prevValue, (%s.Get%s(target)))" data.FullName ap.Name + w.printfn " | struct (_, ValueSome newValue) ->" + w.printfn " %s.Set%s(target, (newValue.Create() :?> %s))" data.FullName ap.Name ap.OriginalType + w.printfn " | struct (ValueSome _, ValueNone) ->" + w.printfn " %s.Set%s(target, null)" data.FullName ap.Name + w.printfn " | struct (ValueNone, ValueNone) -> ()" + + elif not (System.String.IsNullOrWhiteSpace(ap.UpdateCode)) then + w.printfn " %s prev%sOpt curr%sOpt targetChild" ap.UniqueName ap.UniqueName ap.UpdateCode + + else + w.printfn " match struct (prev%sOpt, curr%sOpt) with" ap.UniqueName ap.UniqueName + w.printfn " | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> ()" + w.printfn " | struct (_, ValueSome currValue) -> target.SetValue(%s.%sProperty, %s currValue)" data.FullName ap.Name ap.ConvertModelToValue + w.printfn " | struct (ValueSome _, ValueNone) -> target.ClearValue(%s.%sProperty)" data.FullName ap.Name + w.printfn " | _ -> ()" + + printUpdateBaseIfNeeded " " false + + w.printfn " | _ ->" + printUpdateBaseIfNeeded " " true + + w.printfn "" + w + let generateUpdateFunction (data: UpdateData) (w: StringWriter) = let generateProperties (properties: UpdateProperty array) = @@ -111,90 +168,48 @@ module CodeGenerator = w.printfn " Collections.updateChildren prev%sOpt curr%sOpt target.%s" p.UniqueName p.UniqueName p.Name w.printfn " (fun x -> x.Create() :?> %s)" collectionDataElementType w.printfn " Collections.updateChild" - w.printfn " (match registry.TryGetValue(%s.KeyValue) with true, func -> func | false, _ -> (fun _ _ _ -> ()))" attributeKey + w.printfn " (fun prevChildOpt currChild targetChild -> curr.UpdateAttachedPropertiesForAttribute(%s, prevChildOpt, currChild, targetChild))" attributeKey | Some _ when hasApply -> w.printfn " %s prev%sOpt curr%sOpt target" p.UpdateCode p.UniqueName p.UniqueName - w.printfn " (match registry.TryGetValue(%s.KeyValue) with true, func -> func | false, _ -> (fun _ _ _ -> ()))" attributeKey + w.printfn " (fun prevChildOpt currChild targetChild -> curr.UpdateAttachedPropertiesForAttribute(%s, prevChildOpt, currChild, targetChild))" attributeKey | _ -> // If the type is ViewElement, then it's a type from the model // Issue recursive calls to "Create" and "UpdateIncremental" if p.ModelType = "ViewElement" && not hasApply then - w.printfn " match prev%sOpt, curr%sOpt with" p.UniqueName p.UniqueName + w.printfn " match struct (prev%sOpt, curr%sOpt) with" p.UniqueName p.UniqueName w.printfn " // For structured objects, dependsOn on reference equality" - w.printfn " | ValueSome prevValue, ValueSome newValue when identical prevValue newValue -> ()" - w.printfn " | ValueSome prevValue, ValueSome newValue when canReuseView prevValue newValue ->" + w.printfn " | struct (ValueSome prevValue, ValueSome newValue) when identical prevValue newValue -> ()" + w.printfn " | struct (ValueSome prevValue, ValueSome newValue) when canReuseView prevValue newValue ->" w.printfn " newValue.UpdateIncremental(prevValue, target.%s)" p.Name - w.printfn " | _, ValueSome newValue ->" + w.printfn " | struct (_, ValueSome newValue) ->" w.printfn " target.%s <- (newValue.Create() :?> %s)" p.Name p.OriginalType - w.printfn " | ValueSome _, ValueNone ->" + w.printfn " | struct (ValueSome _, ValueNone) ->" w.printfn " target.%s <- null" p.Name - w.printfn " | ValueNone, ValueNone -> ()" + w.printfn " | struct (ValueNone, ValueNone) -> ()" // Explicit update code elif not (System.String.IsNullOrWhiteSpace(p.UpdateCode)) then w.printfn " %s prev%sOpt curr%sOpt target" p.UpdateCode p.UniqueName p.UniqueName else - w.printfn " match prev%sOpt, curr%sOpt with" p.UniqueName p.UniqueName - w.printfn " | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> ()" - w.printfn " | _, ValueSome currValue -> target.%s <- %s currValue" p.Name p.ConvertModelToValue + w.printfn " match struct (prev%sOpt, curr%sOpt) with" p.UniqueName p.UniqueName + w.printfn " | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> ()" + w.printfn " | struct (_, ValueSome currValue) -> target.%s <- %s currValue" p.Name p.ConvertModelToValue if p.DefaultValue = "" then - w.printfn " | ValueSome _, ValueNone -> target.ClearValue %s.%sProperty" data.FullName p.Name + w.printfn " | struct (ValueSome _, ValueNone) -> target.ClearValue %s.%sProperty" data.FullName p.Name else - w.printfn " | ValueSome _, ValueNone -> target.%s <- %s" p.Name p.DefaultValue - w.printfn " | ValueNone, ValueNone -> ()" + w.printfn " | struct (ValueSome _, ValueNone) -> target.%s <- %s" p.Name p.DefaultValue + w.printfn " | struct (ValueNone, ValueNone) -> ()" - w.printfn " static member Update%s (registry: System.Collections.Generic.Dictionary ViewElement -> obj -> unit>, prevOpt: ViewElement voption, curr: ViewElement, target: %s) = " data.Name data.FullName - - // Attached properties updaters - if data.PropertiesWithAttachedProperties.Length > 0 then - for p in data.PropertiesWithAttachedProperties do - let targetChildType = p.CollectionDataElementType |> Option.defaultValue "obj" - w.printfn " let update%sAttachedProperties overrideFunc (prevChildOpt: ViewElement voption) (newChild: ViewElement) (targetChild: %s) =" p.UniqueName targetChildType - for ap in p.AttachedProperties do - let hasApply = not (System.String.IsNullOrWhiteSpace(ap.ConvertModelToValue)) || not (System.String.IsNullOrWhiteSpace(ap.UpdateCode)) - let attributeKey = getAttributeKey ap.CustomAttributeKey ap.UniqueName - - w.printfn " let prev%sOpt = match prevChildOpt with ValueNone -> ValueNone | ValueSome prevChild -> prevChild.TryGetAttributeKeyed<%s>(%s)" ap.UniqueName ap.ModelType attributeKey - w.printfn " let curr%sOpt = newChild.TryGetAttributeKeyed<%s>(%s)" ap.UniqueName ap.ModelType attributeKey - - if ap.ModelType = "ViewElement" && not hasApply then - w.printfn " match prev%sOpt, curr%sOpt with" ap.UniqueName ap.UniqueName - w.printfn " // For structured objects, dependsOn on reference equality" - w.printfn " | ValueSome prevValue, ValueSome newValue when identical prevValue newValue -> ()" - w.printfn " | ValueSome prevValue, ValueSome newValue when canReuseView prevValue newValue ->" - w.printfn " newValue.UpdateIncremental(prevValue, (%s.Get%s(targetChild)))" data.FullName ap.Name - w.printfn " | _, ValueSome newValue ->" - w.printfn " %s.Set%s(targetChild, (newValue.Create() :?> %s))" data.FullName ap.Name ap.OriginalType - w.printfn " | ValueSome _, ValueNone ->" - w.printfn " %s.Set%s(targetChild, null)" data.FullName ap.Name - w.printfn " | ValueNone, ValueNone -> ()" - - elif not (System.String.IsNullOrWhiteSpace(ap.UpdateCode)) then - w.printfn " %s prev%sOpt curr%sOpt targetChild" ap.UniqueName ap.UniqueName ap.UpdateCode - - else - w.printfn " match prev%sOpt, curr%sOpt with" ap.UniqueName ap.UniqueName - w.printfn " | ValueSome prevChildValue, ValueSome currChildValue when prevChildValue = currChildValue -> ()" - w.printfn " | _, ValueSome currChildValue -> targetChild.SetValue(%s.%sProperty, %s currChildValue)" data.FullName ap.Name ap.ConvertModelToValue - w.printfn " | ValueSome _, ValueNone -> targetChild.ClearValue(%s.%sProperty)" data.FullName ap.Name - w.printfn " | _ -> ()" - - w.printfn " overrideFunc prevChildOpt newChild targetChild" - - w.printfn "" - for p in data.PropertiesWithAttachedProperties do - let attributeKey = getAttributeKey p.CustomAttributeKey p.UniqueName - w.printfn " registry.[%s.KeyValue] <- (fun o p c t -> (update%sAttachedProperties o p c (unbox t)))(match registry.TryGetValue(%s.KeyValue) with true, func -> func | false, _ -> (fun _ _ _ -> ()))" attributeKey p.UniqueName attributeKey - w.printfn "" - + w.printfn " static member Update%s (prevOpt: ViewElement voption, curr: ViewElement, target: %s) = " data.Name data.FullName + if (data.ImmediateMembers.Length = 0) then if (data.BaseName.IsNone) then w.printfn " ()" else - w.printfn " ViewBuilders.Update%s (registry, prevOpt, curr, target)" data.BaseName.Value + w.printfn " ViewBuilders.Update%s(prevOpt, curr, target)" data.BaseName.Value else if data.ImmediateMembers.Length > 0 then for m in data.ImmediateMembers do @@ -220,10 +235,10 @@ module CodeGenerator = for e in data.Events do let relatedProperties = e.RelatedProperties - |> Array.map (fun p -> sprintf "(identical prev%sOpt curr%sOpt)" p p) + |> Array.map (fun p -> sprintf "(identicalVOption prev%sOpt curr%sOpt)" p p) |> Array.fold (fun a b -> a + " && " + b) "" - w.printfn " let shouldUpdate%s = not ((identical prev%sOpt curr%sOpt)%s)" e.UniqueName e.UniqueName e.UniqueName relatedProperties + w.printfn " let shouldUpdate%s = not ((identicalVOption prev%sOpt curr%sOpt)%s)" e.UniqueName e.UniqueName e.UniqueName relatedProperties w.printfn " if shouldUpdate%s then" e.UniqueName w.printfn " match prev%sOpt with" e.UniqueName w.printfn " | ValueSome prevValue -> target.%s.RemoveHandler(prevValue)" e.Name @@ -237,7 +252,7 @@ module CodeGenerator = // Update inherited members if data.BaseName.IsSome then w.printfn " // Update inherited members" - w.printfn " ViewBuilders.Update%s (registry, prevOpt, curr, target)" data.BaseName.Value + w.printfn " ViewBuilders.Update%s(prevOpt, curr, target)" data.BaseName.Value // Update properties if data.Properties.Length > 0 then @@ -284,7 +299,7 @@ module CodeGenerator = w.printfn "" w.printfn " let attribBuilder = ViewBuilders.Build%s(0%s)" data.Name membersForBuild w.printfn "" - w.printfn " ViewElement.Create<%s>(ViewBuilders.Create%s, (fun registry prevOpt curr target -> ViewBuilders.Update%s(registry, prevOpt, curr, target)), attribBuilder)" data.FullName data.Name data.Name + w.printfn " ViewElement.Create<%s>(ViewBuilders.Create%s, (fun prev curr target -> ViewBuilders.Update%s(prev, curr, target)), (fun key prev curr target -> ViewBuilders.Update%sAttachedProperties(key, prev, curr, target)), attribBuilder)" data.FullName data.Name data.Name data.Name w.printfn "" @@ -294,6 +309,7 @@ module CodeGenerator = w |> generateBuildFunction typ.Build |> generateCreateFunction typ.Create + |> generateUpdateAttachedPropertiesFunction typ.UpdateAttachedProperties |> generateUpdateFunction typ.Update |> generateConstruct typ.Construct w diff --git a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Models.fs b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Models.fs index fe155df8f..a84b9671e 100644 --- a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Models.fs +++ b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Models.fs @@ -41,7 +41,7 @@ module Models = { Name: string UniqueName: string RelatedProperties: string array } - + type UpdateProperty = { Name: string UniqueName: string @@ -62,13 +62,19 @@ module Models = ModelType: string ConvertModelToValue: string UpdateCode: string } - + type UpdatePropertyWithAttachedProperties = { UniqueName: string CustomAttributeKey: string option CollectionDataElementType: string option AttachedProperties: UpdateAttachedProperty array } + type UpdateAttachedPropertiesData = + { Name: string + FullName: string + BaseName: string option + PropertiesWithAttachedProperties: UpdatePropertyWithAttachedProperties array } + type UpdateData = { Name: string FullName: string @@ -76,8 +82,7 @@ module Models = ImmediateMembers : UpdateMember array Events: UpdateEvent array Properties: UpdateProperty array - PriorityProperties: UpdateProperty array - PropertiesWithAttachedProperties: UpdatePropertyWithAttachedProperties array } + PriorityProperties: UpdateProperty array } type ConstructData = { Name: string @@ -87,6 +92,7 @@ module Models = type BuilderData = { Build: BuildData Create: CreateData option + UpdateAttachedProperties: UpdateAttachedPropertiesData Update: UpdateData Construct: ConstructData option } diff --git a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Preparer.fs b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Preparer.fs index 3afa3ce91..71fa72008 100644 --- a/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Preparer.fs +++ b/Fabulous.CodeGen/src/Fabulous.CodeGen/Generator/Preparer.fs @@ -48,10 +48,38 @@ module Preparer = FullName = boundType.FullName TypeToInstantiate = boundType.TypeToInstantiate } + let toUpdateAttachedPropertiesData (boundType: BoundType) = + let immediatePropertiesWithAttachedProperties = boundType.Properties |> Array.filter (fun p -> not p.IsInherited && p.CollectionData.IsSome && p.CollectionData.Value.AttachedProperties.Length > 0) + + let updatePropertiesWithAttachedProperties = + immediatePropertiesWithAttachedProperties + |> Array.map (fun p -> + { UniqueName = p.UniqueName + CustomAttributeKey = p.CustomAttributeKey + CollectionDataElementType = p.CollectionData |> Option.map (fun c -> c.ElementType) + AttachedProperties = + p.CollectionData + |> Option.map (fun cd -> + cd.AttachedProperties + |> Array.map (fun ap -> + { Name = ap.Name + UniqueName = ap.UniqueName + CustomAttributeKey = ap.CustomAttributeKey + DefaultValue = ap.DefaultValue + OriginalType = ap.OriginalType + ModelType = ap.ModelType + ConvertModelToValue = ap.ConvertModelToValue + UpdateCode = ap.UpdateCode })) + |> Option.defaultValue [||] }) + + { Name = boundType.Name + FullName = boundType.FullName + BaseName = boundType.BaseTypeName + PropertiesWithAttachedProperties = updatePropertiesWithAttachedProperties } + let toUpdateData (boundType: BoundType) = let immediateEvents = boundType.Events |> Array.filter (fun e -> not e.IsInherited && e.CanBeUpdated) let immediateProperties = boundType.Properties |> Array.filter (fun p -> not p.IsInherited && p.CanBeUpdated) - let immediatePropertiesWithAttachedProperties = boundType.Properties |> Array.filter (fun p -> not p.IsInherited && p.CollectionData.IsSome && p.CollectionData.Value.AttachedProperties.Length > 0) let eventMembers = immediateEvents |> Array.map (fun e -> { UniqueName = e.UniqueName; CustomAttributeKey = e.CustomAttributeKey; ModelType = e.ModelType }) let propertyMembers = immediateProperties |> Array.map (fun p -> { UniqueName = p.UniqueName; CustomAttributeKey = p.CustomAttributeKey; ModelType = p.ModelType }) @@ -67,7 +95,7 @@ module Preparer = UniqueName = e.UniqueName RelatedProperties = relatedProperties } ) - + let updateProperties = immediateProperties |> Array.filter (fun p -> not p.HasPriority) @@ -95,27 +123,6 @@ module Preparer = ConvertModelToValue = p.ConvertModelToValue UpdateCode = p.UpdateCode CollectionDataElementType = p.CollectionData |> Option.map (fun c -> c.ElementType) }) - - let updatePropertiesWithAttachedProperties = - immediatePropertiesWithAttachedProperties - |> Array.map (fun p -> - { UniqueName = p.UniqueName - CustomAttributeKey = p.CustomAttributeKey - CollectionDataElementType = p.CollectionData |> Option.map (fun c -> c.ElementType) - AttachedProperties = - p.CollectionData - |> Option.map (fun cd -> - cd.AttachedProperties - |> Array.map (fun ap -> - { Name = ap.Name - UniqueName = ap.UniqueName - CustomAttributeKey = ap.CustomAttributeKey - DefaultValue = ap.DefaultValue - OriginalType = ap.OriginalType - ModelType = ap.ModelType - ConvertModelToValue = ap.ConvertModelToValue - UpdateCode = ap.UpdateCode })) - |> Option.defaultValue [||] }) { Name = boundType.Name FullName = boundType.FullName @@ -123,8 +130,7 @@ module Preparer = ImmediateMembers = immediateMembers Events = updateEvents Properties = updateProperties - PriorityProperties = updatePriorityProperties - PropertiesWithAttachedProperties = updatePropertiesWithAttachedProperties } + PriorityProperties = updatePriorityProperties } let toConstructData (boundType: BoundType) : ConstructData = let properties = boundType.Properties |> Array.map (fun p -> { Name = p.ShortName; InputType = p.InputType } : ConstructType) @@ -138,6 +144,7 @@ module Preparer = let toBuilderData (boundType: BoundType) = { Build = toBuildData boundType Create = if boundType.CanBeInstantiated then Some (toCreateData boundType) else None + UpdateAttachedProperties = toUpdateAttachedPropertiesData boundType Update = toUpdateData boundType Construct = if boundType.CanBeInstantiated then Some (toConstructData boundType) else None } diff --git a/Fabulous.XamarinForms/extensions/FFImageLoading/Xamarin.FFImageLoading.Forms.json b/Fabulous.XamarinForms/extensions/FFImageLoading/Xamarin.FFImageLoading.Forms.json index 49a53088f..611ec425b 100644 --- a/Fabulous.XamarinForms/extensions/FFImageLoading/Xamarin.FFImageLoading.Forms.json +++ b/Fabulous.XamarinForms/extensions/FFImageLoading/Xamarin.FFImageLoading.Forms.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll", + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll", "packages/generator/Xamarin.FFImageLoading/lib/netstandard1.0/FFImageLoading.dll", "packages/generator/Xamarin.FFImageLoading/lib/netstandard1.0/FFImageLoading.Platform.dll", diff --git a/Fabulous.XamarinForms/extensions/Maps/Xamarin.Forms.Maps.json b/Fabulous.XamarinForms/extensions/Maps/Xamarin.Forms.Maps.json index 93c403d0a..cf882eaa8 100644 --- a/Fabulous.XamarinForms/extensions/Maps/Xamarin.Forms.Maps.json +++ b/Fabulous.XamarinForms/extensions/Maps/Xamarin.Forms.Maps.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll", + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll", "packages/generator/Xamarin.Forms.Maps/lib/netstandard2.0/Xamarin.Forms.Maps.dll" ], diff --git a/Fabulous.XamarinForms/extensions/OxyPlot/OxyPlot.Xamarin.Forms.json b/Fabulous.XamarinForms/extensions/OxyPlot/OxyPlot.Xamarin.Forms.json index 08b912291..b5dd36066 100644 --- a/Fabulous.XamarinForms/extensions/OxyPlot/OxyPlot.Xamarin.Forms.json +++ b/Fabulous.XamarinForms/extensions/OxyPlot/OxyPlot.Xamarin.Forms.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll", + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll", "packages/generator/OxyPlot.Core/lib/netstandard1.0/OxyPlot.dll", "packages/generator/OxyPlot.Xamarin.Forms/lib/portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10/OxyPlot.Xamarin.Forms.dll" diff --git a/Fabulous.XamarinForms/extensions/SkiaSharp/SkiaSharp.Views.Forms.json b/Fabulous.XamarinForms/extensions/SkiaSharp/SkiaSharp.Views.Forms.json index 197c9ec7a..f9a506078 100644 --- a/Fabulous.XamarinForms/extensions/SkiaSharp/SkiaSharp.Views.Forms.json +++ b/Fabulous.XamarinForms/extensions/SkiaSharp/SkiaSharp.Views.Forms.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll", + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll", "packages/generator/SkiaSharp/lib/netstandard1.3/SkiaSharp.dll", "packages/generator/SkiaSharp.Views.Forms/ref/netstandard1.3/SkiaSharp.Views.Forms.dll" diff --git a/Fabulous.XamarinForms/extensions/VideoManager/Plugin.MediaManager.Forms.json b/Fabulous.XamarinForms/extensions/VideoManager/Plugin.MediaManager.Forms.json index 2b336b883..92c13817c 100644 --- a/Fabulous.XamarinForms/extensions/VideoManager/Plugin.MediaManager.Forms.json +++ b/Fabulous.XamarinForms/extensions/VideoManager/Plugin.MediaManager.Forms.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll", + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll", "packages/generator/Plugin.MediaManager/lib/netstandard2.0/MediaManager.dll", "packages/generator/Plugin.MediaManager.Forms/lib/netstandard2.0/MediaManager.Forms.dll" diff --git a/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/ShadowEffect.fs b/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/ShadowEffect.fs index 6fa58246a..2f26efafe 100644 --- a/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/ShadowEffect.fs +++ b/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/ShadowEffect.fs @@ -37,11 +37,13 @@ module ShadowEffectViewExtension = let create () = ShadowEffect() - let update _ (prevOpt: ViewElement voption) (source: ViewElement) (target: ShadowEffect) = + let update (prevOpt: ViewElement voption) (source: ViewElement) (target: ShadowEffect) = source.UpdatePrimitive(prevOpt, target, RadiusAttribKey, (fun target v -> target.Radius <- v)) source.UpdatePrimitive(prevOpt, target, ColorAttribKey, (fun target v -> target.Color <- v)) source.UpdatePrimitive(prevOpt, target, DistanceXAttribKey, (fun target v -> target.DistanceX <- v)) source.UpdatePrimitive(prevOpt, target, DistanceYAttribKey, (fun target v -> target.DistanceY <- v)) + + let updateAttachedProperties _ _ _ _ = () - ViewElement.Create(create, update, attribs) + ViewElement.Create(create, update, updateAttachedProperties, attribs) \ No newline at end of file diff --git a/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/TestLabel.fs b/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/TestLabel.fs index add2deec7..53c9b65d1 100644 --- a/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/TestLabel.fs +++ b/Fabulous.XamarinForms/samples/AllControls/AllControls/Samples/TestLabel.fs @@ -27,10 +27,12 @@ module TestLabel = let create () = Xamarin.Forms.Label() // The incremental update method - let update registry (prevOpt: ViewElement voption) (source: ViewElement) (target: Xamarin.Forms.Label) = - ViewBuilders.UpdateView(registry, prevOpt, source, target) + let update (prevOpt: ViewElement voption) (source: ViewElement) (target: Xamarin.Forms.Label) = + ViewBuilders.UpdateView(prevOpt, source, target) source.UpdatePrimitive(prevOpt, target, TestLabelTextAttribKey, (fun target v -> target.Text <- v)) source.UpdatePrimitive(prevOpt, target, TestLabelFontFamilyAttribKey, (fun target v -> target.FontFamily <- v)) - ViewElement.Create(create, update, attribs) + let updateAttachedProperties _ _ _ _ = () + + ViewElement.Create(create, update, updateAttachedProperties, attribs) diff --git a/Fabulous.XamarinForms/samples/FabulousWeather/FabulousWeather/PancakeViewExtensions.fs b/Fabulous.XamarinForms/samples/FabulousWeather/FabulousWeather/PancakeViewExtensions.fs index 9453ac543..acf5a91ad 100644 --- a/Fabulous.XamarinForms/samples/FabulousWeather/FabulousWeather/PancakeViewExtensions.fs +++ b/Fabulous.XamarinForms/samples/FabulousWeather/FabulousWeather/PancakeViewExtensions.fs @@ -64,14 +64,15 @@ module PancakeViewExtensions = let create () = Xamarin.Forms.PancakeView.PancakeView() // The incremental update method - let update registry (prev: ViewElement voption) (source: ViewElement) (target: Xamarin.Forms.PancakeView.PancakeView) = - ViewBuilders.UpdateView(registry, prev,source,target) + let update (prev: ViewElement voption) (source: ViewElement) (target: Xamarin.Forms.PancakeView.PancakeView) = + ViewBuilders.UpdateView(prev,source,target) source.UpdateElement(prev,target, pancakeContentAttribKey,(fun target -> target.Content), (fun target v -> target.Content <- v)) source.UpdatePrimitive(prev, target, backgroundGradientStartColorAttribKey, (fun target v -> target.BackgroundGradientStartColor <- v)) source.UpdatePrimitive(prev, target, backgroundGradientEndColorAttribKey, (fun target v -> target.BackgroundGradientEndColor <- v)) source.UpdatePrimitive(prev, target, paddingAttribKey, (fun target v -> target.Padding <- v)) source.UpdatePrimitive(prev, target, cornerRadiusKey, (fun target v -> target.CornerRadius <- v)) source.UpdatePrimitive(prev, target, backgroundGradientAngleKey, (fun target v -> target.BackgroundGradientAngle <- v)) - - ViewElement.Create(create, update, attribs) \ No newline at end of file + let updateAttachedProperties _ _ _ _ = () + + ViewElement.Create(create, update, updateAttachedProperties, attribs) \ No newline at end of file diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Collections.fs b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Collections.fs index 09e27028f..5c306d7e5 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Collections.fs +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Collections.fs @@ -6,20 +6,62 @@ open System.Collections.Generic open System.Collections.ObjectModel open Xamarin.Forms open Xamarin.Forms.Shapes +open System.Buffers /// This module contains the update logic for the controls with children module Collections = + [] type Operation<'T> = - | Insert of index: int * element: 'T - | Move of oldIndex: int * newIndex: int - | Update of index: int * prev: 'T * curr: 'T - | MoveAndUpdate of oldIndex: int * prev: 'T * newIndex: int * curr: 'T - | Delete of oldIndex: int - - type DiffResult<'T> = - | NoChange - | ClearCollection - | Operations of Operation<'T> list + | Insert of insertIndex: int * element: 'T + | Move of moveOldIndex: int * moveNewIndex: int + | Update of updateIndex: int * updatePrev: 'T * updateCurr: 'T + | MoveAndUpdate of moveAndUpdateOldIndex: int * moveAndUpdateprev: 'T * moveAndUpdatenewIndex: int * moveAndUpdatecurr: 'T + | Delete of deleteOldIndex: int + + let isMatch canReuse aggressiveReuseMode currIndex newChild (struct (index, reusableChild)) = + canReuse reusableChild newChild + && (aggressiveReuseMode || index = currIndex) + + let rec tryFindRec canReuse aggressiveReuseMode currIndex newChild (reusableElements: struct (int * 'T)[]) (reusableElementsCount: int) index = + if index >= reusableElementsCount then + ValueNone + elif isMatch canReuse aggressiveReuseMode currIndex newChild reusableElements.[index] then + let struct (prevIndex, prevChild) = reusableElements.[index] + ValueSome (struct (index, prevIndex, prevChild)) + else + tryFindRec canReuse aggressiveReuseMode currIndex newChild reusableElements reusableElementsCount (index + 1) + + let tryFindReusableElement canReuse aggressiveReuseMode currIndex newChild reusableElements reusableElementsCount = + tryFindRec canReuse aggressiveReuseMode currIndex newChild reusableElements reusableElementsCount 0 + + let deleteAt index (arr: 'T[]) arrCount = + if index + 1 >= arrCount then + () + else + for i = index to arrCount - 1 do + arr.[i] <- arr.[i + 1] + + let rec canReuseChildOfRec keyOf canReuse prevChild (coll: 'T[]) key i = + if i >= coll.Length then + false + elif keyOf coll.[i] = ValueSome key && canReuse prevChild coll.[i] then + true + else + canReuseChildOfRec keyOf canReuse prevChild coll key (i + 1) + + let canReuseChildOf keyOf canReuse prevChild (coll: 'T[]) key = + canReuseChildOfRec keyOf canReuse prevChild coll key 0 + + let rec isIdenticalRec identical prevChild (coll: 'T[]) i = + if i >= coll.Length then + false + elif identical prevChild coll.[i] then + true + else + isIdenticalRec identical prevChild coll (i + 1) + + let isIdentical identical prevChild (coll: 'T[]) = + isIdenticalRec identical prevChild coll 0 /// Returns a list of operations to apply to go from the initial list to the new list /// @@ -31,171 +73,172 @@ module Collections = /// In the non-aggressive reuse mode, the algorithm will try to reuse a reusable element only if it is at the same index let diff<'T when 'T : equality> (aggressiveReuseMode: bool) + (prevCollLength: int) (prevCollOpt: 'T[] voption) - (collOpt: 'T[] voption) + (coll: 'T[]) (keyOf: 'T -> string voption) (canReuse: 'T -> 'T -> bool) - = - match prevCollOpt, collOpt with - | ValueNone, ValueNone -> NoChange - | ValueSome prevColl, ValueSome newColl when identical prevColl newColl -> NoChange - | ValueSome prevColl, ValueSome newColl when prevColl <> null && newColl <> null && prevColl.Length = 0 && newColl.Length = 0 -> NoChange - | ValueSome _, ValueNone -> ClearCollection - | ValueSome _, ValueSome coll when (coll = null || coll.Length = 0) -> ClearCollection - | _, ValueSome coll -> - // Separate the previous elements into 4 lists - // The ones whose instances have been reused (dependsOn) - // The ones whose keys have been reused and should be updated - // The ones whose keys have not been reused and should be discarded - // The rest which can be reused by any other element - let identicalElements = Dictionary<'T, int>() - let keyedElements = Dictionary() - let reusableElements = ResizeArray() - let discardedElements = ResizeArray() - if prevCollOpt.IsSome && prevCollOpt.Value.Length > 0 then - for prevIndex in 0 .. prevCollOpt.Value.Length - 1 do - let prevChild = prevCollOpt.Value.[prevIndex] - if coll |> Array.exists (identical prevChild) then - identicalElements.Add(prevChild, prevIndex) |> ignore + (workingSet: Operation<'T>[]) + = + let mutable workingSetIndex = 0 + + // Separate the previous elements into 4 lists + // The ones whose instances have been reused (dependsOn) + // The ones whose keys have been reused and should be updated + // The ones whose keys have not been reused and should be discarded + // The rest which can be reused by any other element + let identicalElements = Dictionary<'T, int>() + let keyedElements = Dictionary() + let reusableElements = ArrayPool.Shared.Rent(prevCollLength) + let discardedElements = ArrayPool.Shared.Rent(prevCollLength) + + let mutable reusableElementsCount = 0 + let mutable discardedElementsCount = 0 + + if prevCollOpt.IsSome && prevCollOpt.Value.Length > 0 then + for prevIndex in 0 .. prevCollOpt.Value.Length - 1 do + let prevChild = prevCollOpt.Value.[prevIndex] + if isIdentical identical prevChild coll then + identicalElements.Add(prevChild, prevIndex) |> ignore + else + match keyOf prevChild with + | ValueSome key when canReuseChildOf keyOf canReuse prevChild coll key -> + keyedElements.Add(key, struct (prevIndex, prevChild)) + | ValueNone -> + reusableElements.[reusableElementsCount] <- struct (prevIndex, prevChild) + reusableElementsCount <- reusableElementsCount + 1 + | ValueSome _ -> + discardedElements.[discardedElementsCount] <- prevIndex + discardedElementsCount <- discardedElementsCount + 1 + + for i in 0 .. coll.Length - 1 do + let newChild = coll.[i] + + // Check if the same instance was reused (dependsOn), if so just move the element to the correct index + match identicalElements.TryGetValue(newChild) with + | (true, prevIndex) -> + if prevIndex <> i then + workingSet.[workingSetIndex] <- Move (prevIndex, i) + workingSetIndex <- workingSetIndex + 1 + | _ -> + // If the key existed previously, reuse the previous element + match keyOf newChild with + | ValueSome key when keyedElements.ContainsKey(key) -> + let struct (prevIndex, prevChild) = keyedElements.[key] + if prevIndex <> i then + workingSet.[workingSetIndex] <- MoveAndUpdate (prevIndex, prevChild, i, newChild) + workingSetIndex <- workingSetIndex + 1 else - let canReuseChildOf key = - coll - |> Array.exists (fun newChild -> - keyOf newChild = ValueSome key - && canReuse prevChild newChild - ) - - match keyOf prevChild with - | ValueSome key when canReuseChildOf key -> - keyedElements.Add(key, (prevIndex, prevChild)) - | ValueNone -> - reusableElements.Add((prevIndex, prevChild)) - | ValueSome _ -> - discardedElements.Add(prevIndex) - - let operations = - [ for i in 0 .. coll.Length - 1 do - let newChild = coll.[i] - - // Check if the same instance was reused (dependsOn), if so just move the element to the correct index - match identicalElements.TryGetValue(newChild) with - | (true, prevIndex) -> - if prevIndex <> i then yield Move (prevIndex, i) - | _ -> - // If the key existed previously, reuse the previous element - match keyOf newChild with - | ValueSome key when keyedElements.ContainsKey(key) -> - let prevIndex, prevChild = keyedElements.[key] - if prevIndex <> i then - yield MoveAndUpdate (prevIndex, prevChild, i, newChild) - else - yield Update (i, prevChild, newChild) - - // Otherwise, reuse an old element if possible or create a new one - | _ -> - let isMatch (index, reusableChild) = - canReuse reusableChild newChild - && (aggressiveReuseMode || index = i) - - match reusableElements |> Seq.tryFind isMatch with - | Some ((prevIndex, prevChild) as item) -> - reusableElements.Remove item |> ignore - if prevIndex <> i then - yield MoveAndUpdate (prevIndex, prevChild, i, newChild) - else - yield Update (i, prevChild, newChild) - - | None -> - yield Insert (i, newChild) - - // If we have discarded elements, delete them - if discardedElements.Count > 0 then - for prevIndex in discardedElements do - yield Delete prevIndex - - // If we still have old elements that were not reused, delete them - if reusableElements.Count > 0 then - for prevIndex, _ in reusableElements do - yield Delete prevIndex ] - - if operations.Length = 0 then - NoChange - else - Operations operations + workingSet.[workingSetIndex] <- Update (i, prevChild, newChild) + workingSetIndex <- workingSetIndex + 1 + + // Otherwise, reuse an old element if possible or create a new one + | _ -> + match tryFindReusableElement canReuse aggressiveReuseMode i newChild reusableElements reusableElementsCount with + | ValueSome (struct (reusableIndex, prevIndex, prevChild)) -> + deleteAt reusableIndex reusableElements reusableElementsCount + reusableElementsCount <- reusableElementsCount - 1 + if prevIndex <> i then + workingSet.[workingSetIndex] <- MoveAndUpdate (prevIndex, prevChild, i, newChild) + workingSetIndex <- workingSetIndex + 1 + else + workingSet.[workingSetIndex] <- Update (i, prevChild, newChild) + workingSetIndex <- workingSetIndex + 1 + + | ValueNone -> + workingSet.[workingSetIndex] <- Insert (i, newChild) + workingSetIndex <- workingSetIndex + 1 + + // If we have discarded elements, delete them + if discardedElementsCount > 0 then + for i = 0 to discardedElementsCount - 1 do + workingSet.[workingSetIndex] <- Delete discardedElements.[i] + workingSetIndex <- workingSetIndex + 1 + + // If we still have old elements that were not reused, delete them + if reusableElementsCount > 0 then + for i = 0 to reusableElementsCount - 1 do + let struct (prevIndex, _) = reusableElements.[i] + workingSet.[workingSetIndex] <- Delete prevIndex + workingSetIndex <- workingSetIndex + 1 + + ArrayPool.Shared.Return(reusableElements) + ArrayPool.Shared.Return(discardedElements) + + workingSetIndex + + // Shift all old indices by 1 (down the list) on insert after the inserted position + let shiftForInsert (prevIndices: int[]) index = + for i in 0 .. prevIndices.Length - 1 do + if prevIndices.[i] >= index then + prevIndices.[i] <- prevIndices.[i] + 1 + + // Shift all old indices by -1 (up the list) on delete after the deleted position + let shiftForDelete (prevIndices: int[]) originalIndexInPrevColl prevIndex = + for i in 0 .. prevIndices.Length - 1 do + if prevIndices.[i] > prevIndex then + prevIndices.[i] <- prevIndices.[i] - 1 + prevIndices.[originalIndexInPrevColl] <- -1 + + // Shift all old indices between the previous and new position on move + let shiftForMove (prevIndices: int[]) originalIndexInPrevColl prevIndex newIndex = + for i in 0 .. prevIndices.Length - 1 do + if prevIndex < prevIndices.[i] && prevIndices.[i] <= newIndex then + prevIndices.[i] <- prevIndices.[i] - 1 + else if newIndex <= prevIndices.[i] && prevIndices.[i] < prevIndex then + prevIndices.[i] <- prevIndices.[i] + 1 + prevIndices.[originalIndexInPrevColl] <- newIndex + + // Return an update operation preceded by a move only if actual indices don't match + let moveAndUpdate (prevIndices: int[]) oldIndex prev newIndex curr = + let prevIndex = prevIndices.[oldIndex] + if prevIndex = newIndex then + Update (newIndex, prev, curr) + else + shiftForMove prevIndices oldIndex prevIndex newIndex + MoveAndUpdate (prevIndex, prev, newIndex, curr) /// Reduces the operations of the DiffResult to be applicable to an ObservableCollection. /// /// diff returns all the operations to move from List A to List B. /// Except with ObservableCollection, we're forced to apply the changes one after the other, changing the indices /// So this algorithm compensates this offsetting - let adaptDiffForObservableCollection (prevCollLength: int) (diffResult: DiffResult<'T>) : DiffResult<'T> = - match diffResult with - | NoChange -> NoChange - | ClearCollection -> ClearCollection - | Operations operations -> - let prevIndices = Array.init prevCollLength id - - // Shift all old indices by 1 (down the list) on insert after the inserted position - let shiftForInsert index = - for i in 0 .. prevIndices.Length - 1 do - if prevIndices.[i] >= index then - prevIndices.[i] <- prevIndices.[i] + 1 - - // Shift all old indices by -1 (up the list) on delete after the deleted position - let shiftForDelete originalIndexInPrevColl prevIndex = - for i in 0 .. prevIndices.Length - 1 do - if prevIndices.[i] > prevIndex then - prevIndices.[i] <- prevIndices.[i] - 1 - prevIndices.[originalIndexInPrevColl] <- -1 - - // Shift all old indices between the previous and new position on move - let shiftForMove originalIndexInPrevColl prevIndex newIndex = - for i in 0 .. prevIndices.Length - 1 do - if prevIndex < prevIndices.[i] && prevIndices.[i] <= newIndex then - prevIndices.[i] <- prevIndices.[i] - 1 - else if newIndex <= prevIndices.[i] && prevIndices.[i] < prevIndex then - prevIndices.[i] <- prevIndices.[i] + 1 - prevIndices.[originalIndexInPrevColl] <- newIndex - - // Return an update operation preceded by a move only if actual indices don't match - let moveAndUpdate oldIndex prev newIndex curr = + let adaptDiffForObservableCollection (prevCollLength: int) (workingSet: Operation<'T>[]) (workingSetIndex: int) = + let prevIndices = Array.init prevCollLength id + + let mutable position = 0 + + for i = 0 to workingSetIndex - 1 do + match workingSet.[i] with + | Insert (index, element) -> + workingSet.[position] <- Insert (index, element) + position <- position + 1 + shiftForInsert prevIndices index + + | Move (oldIndex, newIndex) -> + // Prevent a move if the actual indices match let prevIndex = prevIndices.[oldIndex] - if prevIndex = newIndex then - Update (newIndex, prev, curr) - else - shiftForMove oldIndex prevIndex newIndex - MoveAndUpdate (prevIndex, prev, newIndex, curr) - - let operations = - [ for op in operations do - match op with - | Insert (index, element) -> - yield Insert (index, element) - shiftForInsert index - - | Move (oldIndex, newIndex) -> - // Prevent a move if the actual indices match - let prevIndex = prevIndices.[oldIndex] - if prevIndex <> newIndex then - yield (Move (prevIndex, newIndex)) - shiftForMove oldIndex prevIndex newIndex - - | Update (index, prev, curr) -> - yield moveAndUpdate index prev index curr - - | MoveAndUpdate (oldIndex, prev, newIndex, curr) -> - yield moveAndUpdate oldIndex prev newIndex curr - - | Delete oldIndex -> - let prevIndex = prevIndices.[oldIndex] - yield Delete prevIndex - shiftForDelete oldIndex prevIndex ] - - if operations.Length = 0 then - NoChange - else - Operations operations + if prevIndex <> newIndex then + workingSet.[position] <- (Move (prevIndex, newIndex)) + position <- position + 1 + shiftForMove prevIndices oldIndex prevIndex newIndex + + | Update (index, prev, curr) -> + workingSet.[position] <- moveAndUpdate prevIndices index prev index curr + position <- position + 1 + + | MoveAndUpdate (oldIndex, prev, newIndex, curr) -> + workingSet.[position] <- moveAndUpdate prevIndices oldIndex prev newIndex curr + position <- position + 1 + + | Delete oldIndex -> + let prevIndex = prevIndices.[oldIndex] + workingSet.[position] <- Delete prevIndex + position <- position + 1 + shiftForDelete prevIndices oldIndex prevIndex + + position /// Incremental list maintenance: given a collection, and a previous version of that collection, perform /// a reduced number of clear/add/remove/insert operations @@ -208,19 +251,25 @@ module Collections = (canReuse: 'T -> 'T -> bool) (create: 'T -> 'TargetT) (update: 'T -> 'T -> 'TargetT -> unit) // Incremental element-wise update, only if element reuse is allowed - (attach: 'T voption -> 'T -> 'TargetT -> unit) // adjust attached properties + (attach: 'T voption -> 'T -> obj -> unit) // adjust attached properties = - let diffResult = - diff aggressiveReuseMode prevCollOpt collOpt keyOf canReuse - |> adaptDiffForObservableCollection (match prevCollOpt with ValueNone -> 0 | ValueSome c -> c.Length) - - match diffResult with - | NoChange -> () - | ClearCollection -> targetColl.Clear() - | Operations operations -> - for op in operations do - match op with + match struct (prevCollOpt, collOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevColl, ValueSome newColl) when identical prevColl newColl -> () + | struct (ValueSome prevColl, ValueSome newColl) when prevColl <> null && newColl <> null && prevColl.Length = 0 && newColl.Length = 0 -> () + | struct (ValueSome _, ValueNone) -> targetColl.Clear() + | struct (ValueSome _, ValueSome coll) when (coll = null || coll.Length = 0) -> targetColl.Clear() + | struct (_, ValueSome coll) -> + let prevCollLength = (match prevCollOpt with ValueNone -> 0 | ValueSome c -> c.Length) + let workingSet = ArrayPool>.Shared.Rent(prevCollLength + coll.Length) + + let operationsCount = + diff aggressiveReuseMode prevCollLength prevCollOpt coll keyOf canReuse workingSet + |> adaptDiffForObservableCollection prevCollLength workingSet + + for i = 0 to operationsCount - 1 do + match workingSet.[i] with | Insert (index, element) -> let child = create element attach ValueNone element child @@ -246,6 +295,8 @@ module Collections = | Delete index -> targetColl.RemoveAt(index) |> ignore + ArrayPool>.Shared.Return(workingSet) + let updateChildren prevCollOpt collOpt target create update attach = updateCollection true prevCollOpt collOpt target ViewHelpers.tryGetKey ViewHelpers.canReuseView create update attach diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.fsproj b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.fsproj index 5f7b6f9c0..68eb9a732 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.fsproj +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.fsproj @@ -19,6 +19,7 @@ + diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewConverters.fs b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewConverters.fs index 78bebfb62..e1b0ff7a4 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewConverters.fs +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewConverters.fs @@ -7,6 +7,7 @@ open System.Collections open System.Collections.Generic open System.IO open System.Collections.ObjectModel +open Fabulous module CollectionHelpers = /// Try and find a specific ListView item @@ -95,6 +96,21 @@ module ViewConverters = items.[i] :> obj else null + + let convertXamarinFormsColor (v: Xamarin.Forms.Color) = + match StructMemoizations.TryGetValue(v) with + | ValueSome value -> value + | ValueNone -> StructMemoizations.Add(v) + + let convertXamarinFormsThickness (v: Xamarin.Forms.Thickness) = + match StructMemoizations.TryGetValue(v) with + | ValueSome value -> value + | ValueNone -> StructMemoizations.Add(v) + + let convertXamarinFormsLayoutOptions (v: Xamarin.Forms.LayoutOptions) = + match StructMemoizations.TryGetValue(v) with + | ValueSome value -> value + | ValueNone -> StructMemoizations.Add(v) ///////////////// diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewHelpers.fs b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewHelpers.fs index 28eca01de..d23990745 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewHelpers.fs +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewHelpers.fs @@ -11,6 +11,12 @@ open System.Threading module ViewHelpers = /// Checks whether two objects are reference-equal let identical (x: 'T) (y:'T) = System.Object.ReferenceEquals(x, y) + + let identicalVOption (x: 'T voption) (y: 'T voption) = + match struct (x, y) with + | struct (ValueNone, ValueNone) -> true + | struct (ValueSome x1, ValueSome y1) when identical x1 y1 -> true + | _ -> false /// Checks whether an underlying control can be reused given the previous and new view elements let rec canReuseView (prevChild: ViewElement) (newChild: ViewElement) = @@ -134,15 +140,17 @@ module ViewHelpers = item // The update method - let update _ (prevOpt: ViewElement voption) (source: ViewElement) (target: obj) = + let update (prevOpt: ViewElement voption) (source: ViewElement) (target: obj) = let state = unbox<'State> ((snd (localStateTable.TryGetValue(target))).Value) let contents = source.TryGetAttributeKeyed(ContentsAttribKey).Value let realSource = contents state realSource.Update(prevOpt, source, target) match onUpdate with None -> () | Some f -> f state target + let updateAttachedProperties _key _prevOpt _source _target = () + // The element - ViewElement.Create(create, update, attribs) + ViewElement.Create(create, update, updateAttachedProperties, attribs) static member OnCreate (contents : ViewElement, onCreate: (obj -> unit)) = View.Stateful (init = (fun () -> ()), contents = (fun _ -> contents), onCreate = (fun _ obj -> onCreate obj)) @@ -168,6 +176,7 @@ module ViewHelpers = let attribs = AttributesBuilder(0) let create () = box externalObj let update (_prevOpt: ViewElement voption) (_source: ViewElement) (_target: obj) = () - let res = ViewElement(externalObj.GetType(), create, update, attribs) + let updateAttachedProperties _key _prevOpt _curr _target = () + let res = ViewElement(externalObj.GetType(), create, update, updateAttachedProperties, attribs) externalsTable.Add(externalObj, res) res \ No newline at end of file diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewUpdaters.fs b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewUpdaters.fs index e77a5a7b3..afdbe101b 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewUpdaters.fs +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/ViewUpdaters.fs @@ -9,21 +9,22 @@ open Xamarin.Forms open Xamarin.Forms.Shapes open Xamarin.Forms.StyleSheets open System.Windows.Input +open System.Diagnostics /// This module contains custom update logic for all kind of properties module ViewUpdaters = // Update a DataTemplate property taking a direct ViewElement let private updateDirectViewElementDataTemplate setValue clearValue getTarget prevValueOpt currValueOpt = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when identical prevValue currValue -> () - | ValueNone, ValueNone -> () - | ValueNone, ValueSome currValue -> + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when identical prevValue currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (ValueNone, ValueSome currValue) -> setValue (DirectViewElementDataTemplate(currValue)) - | ValueSome prevValue, ValueSome currValue -> + | struct (ValueSome prevValue, ValueSome currValue) -> setValue (DirectViewElementDataTemplate(currValue)) let target = getTarget () if target <> null then currValue.UpdateIncremental(prevValue, target) - | ValueSome _, ValueNone -> + | struct (ValueSome _, ValueNone) -> clearValue () /// Update the ShowJumpList property of a GroupedListView control, given previous and current view elements @@ -31,19 +32,19 @@ module ViewUpdaters = let updateTarget enableJumpList = target.GroupShortNameBinding <- (if enableJumpList then Binding("ShortName") else null) - match (prevOpt, currOpt) with - | ValueNone, ValueSome curr -> updateTarget curr - | ValueSome prev, ValueSome curr when prev <> curr -> updateTarget curr - | ValueSome _, ValueNone -> target.GroupShortNameBinding <- null - | _, _ -> () + match struct (prevOpt, currOpt) with + | struct (ValueNone, ValueSome curr) -> updateTarget curr + | struct (ValueSome prev, ValueSome curr) when prev <> curr -> updateTarget curr + | struct (ValueSome _, ValueNone) -> target.GroupShortNameBinding <- null + | _ -> () /// Update the resources of a control, given previous and current view elements describing the resources let updateResources (prevCollOpt: (string * obj) array voption) (collOpt: (string * obj) array voption) (target: Xamarin.Forms.VisualElement) = - match prevCollOpt, collOpt with - | ValueNone, ValueNone -> () - | ValueSome prevColl, ValueSome newColl when identical prevColl newColl -> () - | _, ValueNone -> target.Resources.Clear() - | _, ValueSome coll -> + match struct (prevCollOpt, collOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevColl, ValueSome newColl) when identical prevColl newColl -> () + | struct (_, ValueNone) -> target.Resources.Clear() + | struct (_, ValueSome coll) -> let targetColl = target.Resources if (coll = null || coll.Length = 0) then targetColl.Clear() @@ -71,11 +72,11 @@ module ViewUpdaters = // Note, style sheets can't be removed // Note, style sheets are compared by object identity let updateStyleSheets (prevCollOpt: StyleSheet array voption) (collOpt: StyleSheet array voption) (target: Xamarin.Forms.VisualElement) = - match prevCollOpt, collOpt with - | ValueNone, ValueNone -> () - | ValueSome prevColl, ValueSome newColl when identical prevColl newColl -> () - | _, ValueNone -> target.Resources.Clear() - | _, ValueSome coll -> + match struct (prevCollOpt, collOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevColl, ValueSome newColl) when identical prevColl newColl -> () + | struct (_, ValueNone) -> target.Resources.Clear() + | struct (_, ValueSome coll) -> let targetColl = target.Resources if (coll = null || coll.Length = 0) then targetColl.Clear() @@ -105,11 +106,11 @@ module ViewUpdaters = // Note, styles can't be removed // Note, styles are compared by object identity let updateStyles (prevCollOpt: Style array voption) (collOpt: Style array voption) (target: Xamarin.Forms.VisualElement) = - match prevCollOpt, collOpt with - | ValueNone, ValueNone -> () - | ValueSome prevColl, ValueSome newColl when identical prevColl newColl -> () - | _, ValueNone -> target.Resources.Clear() - | _, ValueSome coll -> + match struct (prevCollOpt, collOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevColl, ValueSome newColl) when identical prevColl newColl -> () + | struct (_, ValueNone) -> target.Resources.Clear() + | struct (_, ValueSome coll) -> let targetColl = target.Resources if (coll = null || coll.Length = 0) then targetColl.Clear() @@ -137,10 +138,10 @@ module ViewUpdaters = /// Incremental NavigationPage maintenance: push/pop the right pages let updateNavigationPages (prevCollOpt: ViewElement[] voption) (collOpt: ViewElement[] voption) (target: NavigationPage) attach = - match prevCollOpt, collOpt with - | ValueSome prevColl, ValueSome newColl when identical prevColl newColl -> () - | _, ValueNone -> failwith "Error while updating NavigationPage pages: the pages collection should never be empty for a NavigationPage" - | _, ValueSome coll -> + match struct (prevCollOpt, collOpt) with + | struct (ValueSome prevColl, ValueSome newColl) when identical prevColl newColl -> () + | struct (_, ValueNone) -> failwith "Error while updating NavigationPage pages: the pages collection should never be empty for a NavigationPage" + | struct (_, ValueSome coll) -> let create (desc: ViewElement) = (desc.Create() :?> Page) if (coll = null || coll.Length = 0) then failwith "Error while updating NavigationPage pages: the pages collection should never be empty for a NavigationPage" @@ -148,15 +149,15 @@ module ViewUpdaters = // Count the existing pages let prevCount = target.Pages |> Seq.length let newCount = coll.Length - printfn "Updating NavigationPage, prevCount = %d, newCount = %d" prevCount newCount + Debug.WriteLine(sprintf "Updating NavigationPage, prevCount = %d, newCount = %d" prevCount newCount) // Remove the excess pages if newCount = 1 && prevCount > 1 then - printfn "Updating NavigationPage --> PopToRootAsync" + Debug.WriteLine(sprintf "Updating NavigationPage --> PopToRootAsync") target.PopToRootAsync() |> ignore elif prevCount > newCount then for i in prevCount - 1 .. -1 .. newCount do - printfn "PopAsync, page number %d" i + Debug.WriteLine(sprintf "PopAsync, page number %d" i) target.PopAsync () |> ignore let n = min prevCount newCount @@ -164,27 +165,27 @@ module ViewUpdaters = for i in 0 .. newCount-1 do let newChild = coll.[i] let prevChildOpt = match prevCollOpt with ValueNone -> ValueNone | ValueSome coll when i < coll.Length && i < n -> ValueSome coll.[i] | _ -> ValueNone - let prevChildOpt, targetChild = + let struct (prevChildOpt, targetChild) = if (match prevChildOpt with ValueNone -> true | ValueSome prevChild -> not (identical prevChild newChild)) then let mustCreate = (i >= n || match prevChildOpt with ValueNone -> true | ValueSome prevChild -> not (ViewHelpers.canReuseView prevChild newChild)) if mustCreate then - //printfn "Creating child %d, prevChildOpt = %A, newChild = %A" i prevChildOpt newChild + Debug.WriteLine(sprintf "Creating child %d, prevChildOpt = %A, newChild = %A" i prevChildOpt newChild) let targetChild = create newChild if i >= n then - printfn "PushAsync, page number %d" i + Debug.WriteLine(sprintf "PushAsync, page number %d" i) target.PushAsync(targetChild) |> ignore else failwith "Error while updating NavigationPage pages: can't change type of one of the pages in the navigation chain during navigation" - ValueNone, targetChild + struct (ValueNone, targetChild) else - printfn "Adjust page number %d" i + Debug.WriteLine(sprintf "Adjust page number %d" i) let targetChild = target.Pages |> Seq.item i newChild.UpdateIncremental(prevChildOpt.Value, targetChild) - prevChildOpt, targetChild + struct (prevChildOpt, targetChild) else - //printfn "Skipping child %d" i + Debug.WriteLine(sprintf "Skipping child %d" i) let targetChild = target.Pages |> Seq.item i - prevChildOpt, targetChild + struct (prevChildOpt, targetChild) attach prevChildOpt newChild targetChild /// Update the OnSizeAllocated callback of a control, given previous and current values @@ -213,21 +214,21 @@ module ViewUpdaters = /// Update the Command and CanExecute properties of a control, given previous and current values let inline updateCommand prevCommandValueOpt commandValueOpt argTransform setter prevCanExecuteValueOpt canExecuteValueOpt target = - match prevCommandValueOpt, prevCanExecuteValueOpt, commandValueOpt, canExecuteValueOpt with - | ValueNone, ValueNone, ValueNone, ValueNone -> () - | ValueSome prevf, ValueNone, ValueSome f, ValueNone when identical prevf f -> () - | ValueSome prevf, ValueSome prevx, ValueSome f, ValueSome x when identical prevf f && prevx = x -> () - | _, _, ValueNone, _ -> setter target null - | _, _, ValueSome f, ValueNone -> setter target (makeCommand (fun () -> f (argTransform target))) - | _, _, ValueSome f, ValueSome k -> setter target (makeCommandCanExecute (fun () -> f (argTransform target)) k) + match struct (prevCommandValueOpt, prevCanExecuteValueOpt, commandValueOpt, canExecuteValueOpt) with + | struct (ValueNone, ValueNone, ValueNone, ValueNone) -> () + | struct (ValueSome prevf, ValueNone, ValueSome f, ValueNone) when identical prevf f -> () + | struct (ValueSome prevf, ValueSome prevx, ValueSome f, ValueSome x) when identical prevf f && prevx = x -> () + | struct (_, _, ValueNone, _) -> setter target null + | struct (_, _, ValueSome f, ValueNone) -> setter target (makeCommand (fun () -> f (argTransform target))) + | struct (_, _, ValueSome f, ValueSome k) -> setter target (makeCommandCanExecute (fun () -> f (argTransform target)) k) /// Update the CurrentPage of a control, given previous and current values let updateMultiPageOfTCurrentPage<'a when 'a :> Xamarin.Forms.Page> prevValueOpt valueOpt (target: Xamarin.Forms.MultiPage<'a>) = - match prevValueOpt, valueOpt with - | ValueNone, ValueNone -> () - | ValueSome prev, ValueSome curr when prev = curr -> () - | ValueSome _, ValueNone -> target.CurrentPage <- Unchecked.defaultof<'a> - | _, ValueSome curr -> target.CurrentPage <- target.Children.[curr] + match struct (prevValueOpt, valueOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prev, ValueSome curr) when prev = curr -> () + | struct (ValueSome _, ValueNone) -> target.CurrentPage <- Unchecked.defaultof<'a> + | struct (_, ValueSome curr) -> target.CurrentPage <- target.Children.[curr] /// Update the Minimum and Maximum values of a slider, given previous and current values let updateSliderMinimumMaximum prevValueOpt valueOpt (target: Xamarin.Forms.Slider) = @@ -243,12 +244,12 @@ module ViewUpdaters = target.ClearValue Slider.MaximumProperty target.ClearValue Slider.MinimumProperty - match prevValueOpt, valueOpt with - | ValueNone, ValueNone -> () - | ValueSome prev, ValueSome curr when prev = curr -> () - | ValueSome prev, ValueSome curr -> updateFunc prev curr - | ValueSome _, ValueNone -> clearValues () - | ValueNone, ValueSome curr -> updateFunc (0.0, 1.0) curr + match struct (prevValueOpt, valueOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prev, ValueSome curr) when prev = curr -> () + | struct (ValueSome prev, ValueSome curr) -> updateFunc prev curr + | struct (ValueSome _, ValueNone) -> clearValues () + | struct (ValueNone, ValueSome curr) -> updateFunc (0.0, 1.0) curr /// Update the Minimum and Maximum values of a stepper, given previous and current values let updateStepperMinimumMaximum prevValueOpt valueOpt (target: Xamarin.Forms.Stepper) = @@ -264,20 +265,20 @@ module ViewUpdaters = target.ClearValue Stepper.MaximumProperty target.ClearValue Stepper.MinimumProperty - match prevValueOpt, valueOpt with - | ValueNone, ValueNone -> () - | ValueSome prev, ValueSome curr when prev = curr -> () - | ValueSome prev, ValueSome curr -> updateFunc prev curr - | ValueSome _, ValueNone -> clearValues () - | ValueNone, ValueSome curr -> updateFunc (0.0, 1.0) curr + match struct (prevValueOpt, valueOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prev, ValueSome curr) when prev = curr -> () + | struct (ValueSome prev, ValueSome curr) -> updateFunc prev curr + | struct (ValueSome _, ValueNone) -> clearValues () + | struct (ValueNone, ValueSome curr) -> updateFunc (0.0, 1.0) curr /// Update the AcceleratorProperty of a MenuItem, given previous and current Accelerator let updateMenuItemAccelerator prevValue currValue (target: Xamarin.Forms.MenuItem) = - match prevValue, currValue with - | ValueNone, ValueNone -> () - | ValueSome prevVal, ValueSome newVal when prevVal = newVal -> () - | _, ValueNone -> target.ClearValue Xamarin.Forms.MenuItem.AcceleratorProperty - | _, ValueSome newVal -> Xamarin.Forms.MenuItem.SetAccelerator(target, Xamarin.Forms.Accelerator.FromString newVal) + match struct (prevValue, currValue) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevVal, ValueSome newVal) when prevVal = newVal -> () + | struct (_, ValueNone) -> target.ClearValue Xamarin.Forms.MenuItem.AcceleratorProperty + | struct (_, ValueSome newVal) -> Xamarin.Forms.MenuItem.SetAccelerator(target, Xamarin.Forms.Accelerator.FromString newVal) /// Trigger ScrollView.ScrollToAsync if needed, given the current values let triggerScrollToAsync _ (currValue: (float * float * AnimationKind) voption) (target: Xamarin.Forms.ScrollView) = @@ -349,133 +350,133 @@ module ViewUpdaters = | _ -> () let updatePageShellSearchHandler prevValueOpt (currValueOpt: ViewElement voption) target = - match prevValueOpt, currValueOpt with - | ValueNone, ValueNone -> () - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueSome prevValue, ValueSome currValue -> + match struct (prevValueOpt, currValueOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueSome prevValue, ValueSome currValue) -> let searchHandler = Shell.GetSearchHandler(target) currValue.UpdateIncremental(prevValue, searchHandler) - | ValueNone, ValueSome currValue -> Shell.SetSearchHandler(target, currValue.Create() :?> Xamarin.Forms.SearchHandler) - | ValueSome _, ValueNone -> target.ClearValue Shell.SearchHandlerProperty + | struct (ValueNone, ValueSome currValue) -> Shell.SetSearchHandler(target, currValue.Create() :?> Xamarin.Forms.SearchHandler) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.SearchHandlerProperty let updateShellBackgroundColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetBackgroundColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.BackgroundColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetBackgroundColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.BackgroundColorProperty let updateShellForegroundColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetForegroundColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.ForegroundColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetForegroundColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.ForegroundColorProperty let updateShellTitleColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTitleColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TitleColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTitleColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TitleColorProperty let updateShellDisabledColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetDisabledColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.DisabledColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetDisabledColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.DisabledColorProperty let updateShellUnselectedColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetUnselectedColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.UnselectedColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetUnselectedColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.UnselectedColorProperty let updateShellTabBarBackgroundColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarBackgroundColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarBackgroundColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarBackgroundColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarBackgroundColorProperty let updateShellTabBarForegroundColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarForegroundColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarForegroundColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarForegroundColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarForegroundColorProperty let updateShellBackButtonBehavior prevValueOpt (currValueOpt: ViewElement voption) target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetBackButtonBehavior(target, currValue.Create() :?> BackButtonBehavior) - | ValueSome _, ValueNone -> target.ClearValue Shell.BackButtonBehaviorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetBackButtonBehavior(target, currValue.Create() :?> BackButtonBehavior) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.BackButtonBehaviorProperty let updateShellTitleView prevValueOpt (currValueOpt: ViewElement voption) target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTitleView(target, currValue.Create() :?> View) - | ValueSome _, ValueNone -> target.ClearValue Shell.TitleViewProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTitleView(target, currValue.Create() :?> View) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TitleViewProperty let updateShellFlyoutBehavior prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetFlyoutBehavior(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.FlyoutBehaviorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetFlyoutBehavior(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.FlyoutBehaviorProperty let updateShellTabBarIsVisible prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarIsVisible(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarIsVisibleProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarIsVisible(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarIsVisibleProperty let updateShellNavBarIsVisible prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetNavBarIsVisible(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.NavBarIsVisibleProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetNavBarIsVisible(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.NavBarIsVisibleProperty let updateShellPresentationMode prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetPresentationMode(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.PresentationModeProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetPresentationMode(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.PresentationModeProperty let updateShellTabBarDisabledColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarDisabledColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarDisabledColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarDisabledColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarDisabledColorProperty let updateShellTabBarTitleColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarTitleColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarTitleColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarTitleColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarTitleColorProperty let updateShellTabBarUnselectedColor prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetTabBarUnselectedColor(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.TabBarUnselectedColorProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetTabBarUnselectedColor(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.TabBarUnselectedColorProperty let updateNavigationPageHasNavigationBar prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> NavigationPage.SetHasNavigationBar(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue NavigationPage.HasNavigationBarProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> NavigationPage.SetHasNavigationBar(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue NavigationPage.HasNavigationBarProperty let updateShellContentContentTemplate prevValueOpt currValueOpt (target : Xamarin.Forms.ShellContent) = updateDirectViewElementDataTemplate @@ -486,40 +487,40 @@ module ViewUpdaters = currValueOpt let updateShellNavBarHasShadow prevValueOpt currValueOpt target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Shell.SetNavBarHasShadow(target, currValue) - | ValueSome _, ValueNone -> target.ClearValue Shell.NavBarHasShadowProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Shell.SetNavBarHasShadow(target, currValue) + | struct (ValueSome _, ValueNone) -> target.ClearValue Shell.NavBarHasShadowProperty let updatePageUseSafeArea (prevValueOpt: bool voption) (currValueOpt: bool voption) (target: Xamarin.Forms.Page) = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(target, currValue) - | ValueSome _, ValueNone -> Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(target, false) + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(target, currValue) + | struct (ValueSome _, ValueNone) -> Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(target, false) let triggerWebViewReload _ curr (target: Xamarin.Forms.WebView) = if curr = ValueSome true then target.Reload() let updateEntryCursorPosition prev curr (target: Xamarin.Forms.Entry) = - match prev, curr with - | ValueNone, ValueNone -> () - | _, ValueSome value -> target.CursorPosition <- value - | ValueSome _, ValueNone -> target.ClearValue Entry.CursorPositionProperty + match struct (prev, curr) with + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome value) -> target.CursorPosition <- value + | struct (ValueSome _, ValueNone) -> target.ClearValue Entry.CursorPositionProperty let updateEntrySelectionLength prev curr (target: Xamarin.Forms.Entry) = - match prev, curr with - | ValueNone, ValueNone -> () - | _, ValueSome value -> target.SelectionLength <- value - | ValueSome _, ValueNone -> target.ClearValue Entry.SelectionLengthProperty + match struct (prev, curr) with + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome value) -> target.SelectionLength <- value + | struct (ValueSome _, ValueNone) -> target.ClearValue Entry.SelectionLengthProperty let updateElementMenu prevValueOpt (currValueOpt: ViewElement voption) target = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | _, ValueSome currValue -> Element.SetMenu(target, currValue.Create() :?> Menu) - | ValueSome _, ValueNone -> target.ClearValue Element.MenuProperty + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (_, ValueSome currValue) -> Element.SetMenu(target, currValue.Create() :?> Menu) + | struct (ValueSome _, ValueNone) -> target.ClearValue Element.MenuProperty // The CarouselView/IndicatorView combo in Xamarin.Forms is special. // A CarouselView can be linked to an IndicatorView, we're using a ViewRef to handle that. @@ -564,19 +565,19 @@ module ViewUpdaters = carouselViewHandlers.Add(key, handler) handler - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueNone -> () - | ValueSome prevValue, ValueNone -> + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prevValue, ValueNone) -> let handler = getHandler() prevValue.ValueChanged.RemoveHandler(handler) removeCarouselViewHandler target linkIndicatorViewToCarouselView target null - | ValueNone, ValueSome currValue -> + | struct (ValueNone, ValueSome currValue) -> let handler = getHandler() currValue.ValueChanged.AddHandler(handler) tryLinkIndicatorViewToCarouselView target currValue - | ValueSome prevValue, ValueSome currValue -> + | struct (ValueSome prevValue, ValueSome currValue) -> let handler = getHandler() prevValue.ValueChanged.RemoveHandler(handler) currValue.ValueChanged.AddHandler(handler) @@ -591,50 +592,50 @@ module ViewUpdaters = currValueOpt let updatePathData prevValueOpt (currValueOpt: InputTypes.Content.Value voption) (target: Xamarin.Forms.Shapes.Path) = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueSome currValue -> + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueSome currValue) -> match currValue with | Content.String str -> target.Data <- PathGeometryConverter().ConvertFromInvariantString(str) :?> Xamarin.Forms.Shapes.Geometry | Content.ViewElement ve -> target.Data <- (ve.Create() :?> Xamarin.Forms.Shapes.Geometry) - | ValueSome prevValue, ValueSome currValue -> - match prevValue, currValue with - | Content.String prevStr, Content.String currStr when prevStr = currStr -> () - | Content.ViewElement prevVe, Content.ViewElement currVe when identical prevVe currVe -> () - | Content.ViewElement prevVe, Content.ViewElement currVe when canReuseView prevVe currVe -> currVe.UpdateIncremental(prevVe, target.Data) - | _, Content.String currStr -> target.Data <- PathGeometryConverter().ConvertFromInvariantString(currStr) :?> Xamarin.Forms.Shapes.Geometry - | _, Content.ViewElement currVe -> target.Data <- (currVe.Create() :?> Xamarin.Forms.Shapes.Geometry) + | struct (ValueSome prevValue, ValueSome currValue) -> + match struct (prevValue, currValue) with + | struct (Content.String prevStr, Content.String currStr) when prevStr = currStr -> () + | struct (Content.ViewElement prevVe, Content.ViewElement currVe) when identical prevVe currVe -> () + | struct (Content.ViewElement prevVe, Content.ViewElement currVe) when canReuseView prevVe currVe -> currVe.UpdateIncremental(prevVe, target.Data) + | struct (_, Content.String currStr) -> target.Data <- PathGeometryConverter().ConvertFromInvariantString(currStr) :?> Xamarin.Forms.Shapes.Geometry + | struct (_, Content.ViewElement currVe) -> target.Data <- (currVe.Create() :?> Xamarin.Forms.Shapes.Geometry) - | ValueSome _, ValueNone -> target.Data.ClearValue(Xamarin.Forms.Shapes.Path.DataProperty) - | ValueNone, ValueNone -> () + | struct (ValueSome _, ValueNone) -> target.Data.ClearValue(Xamarin.Forms.Shapes.Path.DataProperty) + | struct (ValueNone, ValueNone) -> () let updatePathRenderTransform prevValueOpt (currValueOpt: InputTypes.Content.Value voption) (target: Xamarin.Forms.Shapes.Path) = - match prevValueOpt, currValueOpt with - | ValueSome prevValue, ValueSome currValue when prevValue = currValue -> () - | ValueNone, ValueSome currValue -> + match struct (prevValueOpt, currValueOpt) with + | struct (ValueSome prevValue, ValueSome currValue) when prevValue = currValue -> () + | struct (ValueNone, ValueSome currValue) -> match currValue with | Content.String str -> target.RenderTransform <- TransformTypeConverter().ConvertFromInvariantString(str) :?> Xamarin.Forms.Shapes.Transform | Content.ViewElement ve -> target.RenderTransform <- (ve.Create() :?> Xamarin.Forms.Shapes.Transform) - | ValueSome prevValue, ValueSome currValue -> - match prevValue, currValue with - | Content.String prevStr, Content.String currStr when prevStr = currStr -> () - | Content.ViewElement prevVe, Content.ViewElement currVe when identical prevVe currVe -> () - | Content.ViewElement prevVe, Content.ViewElement currVe when canReuseView prevVe currVe -> currVe.UpdateIncremental(prevVe, target.RenderTransform) - | _, Content.String currStr -> target.RenderTransform <- TransformTypeConverter().ConvertFromInvariantString(currStr) :?> Xamarin.Forms.Shapes.Transform - | _, Content.ViewElement currVe -> target.RenderTransform <- (currVe.Create() :?> Xamarin.Forms.Shapes.Transform) + | struct (ValueSome prevValue, ValueSome currValue) -> + match struct (prevValue, currValue) with + | struct (Content.String prevStr, Content.String currStr) when prevStr = currStr -> () + | struct (Content.ViewElement prevVe, Content.ViewElement currVe) when identical prevVe currVe -> () + | struct (Content.ViewElement prevVe, Content.ViewElement currVe) when canReuseView prevVe currVe -> currVe.UpdateIncremental(prevVe, target.RenderTransform) + | struct (_, Content.String currStr) -> target.RenderTransform <- TransformTypeConverter().ConvertFromInvariantString(currStr) :?> Xamarin.Forms.Shapes.Transform + | struct (_, Content.ViewElement currVe) -> target.RenderTransform <- (currVe.Create() :?> Xamarin.Forms.Shapes.Transform) - | ValueSome _, ValueNone -> target.Data.ClearValue(Xamarin.Forms.Shapes.Path.DataProperty) - | ValueNone, ValueNone -> () + | struct (ValueSome _, ValueNone) -> target.Data.ClearValue(Xamarin.Forms.Shapes.Path.DataProperty) + | struct (ValueNone, ValueNone) -> () let inline updatePoints (target: Xamarin.Forms.BindableObject) (bindableProperty: Xamarin.Forms.BindableProperty) (prevOpt: Fabulous.XamarinForms.InputTypes.Points.Value voption) (currOpt: Fabulous.XamarinForms.InputTypes.Points.Value voption) = - match prevOpt, currOpt with - | ValueNone, ValueNone -> () - | ValueSome prev, ValueSome curr when prev = curr -> () - | ValueSome _, ValueNone -> target.ClearValue(bindableProperty) - | _, ValueSome curr -> + match struct (prevOpt, currOpt) with + | struct (ValueNone, ValueNone) -> () + | struct (ValueSome prev, ValueSome curr) when prev = curr -> () + | struct (ValueSome _, ValueNone) -> target.ClearValue(bindableProperty) + | struct (_, ValueSome curr) -> match curr with | Points.String str -> target.SetValue(bindableProperty, PointCollectionConverter().ConvertFromInvariantString(str)) | Points.PointsList lst -> diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Fabulous.XamarinForms.fsproj b/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Fabulous.XamarinForms.fsproj index a0d4014b4..e154f204d 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Fabulous.XamarinForms.fsproj +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Fabulous.XamarinForms.fsproj @@ -20,6 +20,7 @@ + diff --git a/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.json b/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.json index bf828dcca..f70c00621 100644 --- a/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.json +++ b/Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.json @@ -1,7 +1,7 @@ { "assemblies": [ "packages/generator/Xamarin.Forms/lib/netstandard2.0/Xamarin.Forms.Core.dll", - "build_output/Fabulous.XamarinForms/Fabulous.XamarinForms.Core/Fabulous.XamarinForms.Core.dll" + "Fabulous.XamarinForms/src/Fabulous.XamarinForms.Core/bin/Release/netstandard2.0/Fabulous.XamarinForms.Core.dll" ], "outputNamespace": "Fabulous.XamarinForms", "types": [ diff --git a/Fabulous.XamarinForms/templates/content/blank/.template.config/template.json b/Fabulous.XamarinForms/templates/content/blank/.template.config/template.json index e38169b32..b6f566213 100644 --- a/Fabulous.XamarinForms/templates/content/blank/.template.config/template.json +++ b/Fabulous.XamarinForms/templates/content/blank/.template.config/template.json @@ -7,7 +7,7 @@ "Elmish", "Cross-platform" ], - "name": "Fabulous Xamarin.Forms App v0.58.0", + "name": "Fabulous Xamarin.Forms App v0.60.0-preview1", "groupIdentity": "Fabulous.XamarinForms.App", "identity": "Fabulous.XamarinForms.FSharp", "shortName": "fabulous-xf-app", @@ -418,7 +418,7 @@ "type": "parameter", "dataType": "string", "replaces": "FabulousPkgsVersion", - "defaultValue": "0.58.0" + "defaultValue": "0.60.0-preview1" }, "NewtonsoftJsonPkg": { "type": "parameter", diff --git a/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/AdaptDiffTests.fs b/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/AdaptDiffTests.fs index 0329af813..3fa28d4f1 100644 --- a/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/AdaptDiffTests.fs +++ b/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/AdaptDiffTests.fs @@ -7,6 +7,12 @@ open NUnit.Framework open FsUnit module AdaptDiffTests = + let testAdaptDiffForObservableCollection prev = + let prevArray = Array.ofList prev + let workingSetIndex = Collections.adaptDiffForObservableCollection prev.Length prevArray prevArray.Length + prevArray.[0 .. workingSetIndex - 1] + |> List.ofArray + [] let ``Test Reduce``() = let previous = @@ -31,7 +37,7 @@ module AdaptDiffTests = View.Button(key = "Button2_1") View.Button(key = "Button2_2") ] - let diffResult = DiffResult.Operations [ + let diffResult = [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) @@ -43,9 +49,9 @@ module AdaptDiffTests = Update (8, previous.[8], current.[8]) Delete 4 ] - - Collections.adaptDiffForObservableCollection previous.Length diffResult - |> should equal (DiffResult.Operations [ + + testAdaptDiffForObservableCollection diffResult + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) @@ -56,7 +62,7 @@ module AdaptDiffTests = MoveAndUpdate (8, previous.[7], 7, current.[7]) MoveAndUpdate (9, previous.[8], 8, current.[8]) Delete 9 - ]) + ] [] let ``Test Reduce 2``() = @@ -72,7 +78,7 @@ module AdaptDiffTests = View.Button(key = "Button0_1") View.Image(key = "Button0_2") ] - let diffResult = DiffResult.Operations [ + let diffResult = [ Insert (0, current.[0]) Update (1, previous.[1], current.[1]) MoveAndUpdate (4, previous.[4], 2, current.[2]) @@ -81,15 +87,15 @@ module AdaptDiffTests = Delete 3 ] - Collections.adaptDiffForObservableCollection previous.Length diffResult - |> should equal (DiffResult.Operations [ + testAdaptDiffForObservableCollection diffResult + |> should equal [ Insert (0, current.[0]) MoveAndUpdate (2, previous.[1], 1, current.[1]) MoveAndUpdate (5, previous.[4], 2, current.[2]) Delete 3 Delete 3 Delete 3 - ]) + ] [] let ``Test Reduce 3``() = @@ -110,7 +116,7 @@ module AdaptDiffTests = [ View.Button(key = "0") View.Label() ] - let diffResult = DiffResult.Operations [ + let diffResult = [ MoveAndUpdate (2, previous.[2], 0, current.[0]) MoveAndUpdate (0, previous.[0], 1, current.[1]) Delete 1 @@ -124,8 +130,8 @@ module AdaptDiffTests = Delete 10 ] - Collections.adaptDiffForObservableCollection previous.Length diffResult - |> should equal (DiffResult.Operations [ + testAdaptDiffForObservableCollection diffResult + |> should equal [ MoveAndUpdate (2, previous.[2], 0, current.[0]) Update (1, previous.[0], current.[1]) Delete 2 @@ -137,7 +143,7 @@ module AdaptDiffTests = Delete 2 Delete 2 Delete 2 - ]) + ] diff --git a/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/DiffTests.fs b/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/DiffTests.fs index 2a51b5d5b..43e0d0968 100644 --- a/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/DiffTests.fs +++ b/Fabulous.XamarinForms/tests/Fabulous.XamarinForms.Tests/Collections/DiffTests.fs @@ -15,38 +15,22 @@ module DiffTests = match prev with | ValueNone -> ValueNone | ValueSome list -> ValueSome (Array.ofList list) + let currArray = match curr with - | ValueNone -> ValueNone - | ValueSome list -> ValueSome (Array.ofList list) - - Collections.diff true prevArray currArray (fun v -> v.TryGetKey()) (fun prev curr -> canReuseView prev curr) - - /// Going from an undefined state to another undefined state should do nothing - [] - let ``Given previous state = None / current state = None, updateChildren should do nothing``() = - testUpdateChildren ValueNone ValueNone - |> should equal DiffResult.NoChange - - /// Not defining a previously existing list clears all previous controls - [] - let ``Given previous state = 1-2-3 / current state = None, updateChildren should Clear``() = - let previous = - [ View.Label() - View.Label() - View.Label() ] - - testUpdateChildren (ValueSome previous) ValueNone - |> should equal DiffResult.ClearCollection - - /// A non-changing empty list should do nothing - [] - let ``Given previous state = Empty / current state = Empty, updateChildren should do nothing``() = - let previous = [] - let current = [] + | [] -> [||] + | list -> Array.ofList list + + let prevArrayLength = + match prev with + | ValueNone -> 0 + | ValueSome list -> list.Length + + let workingSet = Array.zeroCreate> (prevArrayLength + curr.Length) - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal DiffResult.NoChange + let workingSetLength = Collections.diff true prevArrayLength prevArray currArray (fun v -> v.TryGetKey()) (fun prev curr -> canReuseView prev curr) workingSet + workingSet.[0 .. workingSetLength - 1] + |> List.ofArray /// Adding a new element to an empty list should create the associated control [] @@ -54,22 +38,10 @@ module DiffTests = let previous = [] let current = [ View.Label() ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal - (DiffResult.Operations [ - Insert (0, current.[0]) - ]) - - /// Keeping the exact same state (same instance) should do nothing - [] - let ``Given previous state = 1 / current state = 1 (same reference), updateChildren should do nothing``() = - // To keep the reference the same between 2 states, we use dependsOn - let label = dependsOn () (fun _ _ -> View.Label()) - let previous = [ label ] - let current = [ label ] - - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.NoChange) + testUpdateChildren (ValueSome previous) current + |> should equal [ + Insert (0, current.[0]) + ] /// Keeping the same state (not same instance) should update the existing control nonetheless [] @@ -77,10 +49,10 @@ module DiffTests = let previous = [ View.Label() ] let current = [ View.Label() ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) - ]) + ] /// Replacing an element by another one (same control type) should update the existing control [] @@ -88,23 +60,10 @@ module DiffTests = let previous = [ View.Label(text = "A") ] let current = [ View.Label(text = "B") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) - ]) - - /// Emptying a list should clear all controls - [] - let ``Given previous state = 1-2-3 / current state = Empty, updateChildren should Clear``() = - let previous = - [ View.Label() - View.Label() - View.Label() ] - let current = - [] - - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal DiffResult.ClearCollection + ] /// Keeping elements at the start (not same instance) and removing elements at the end should update the remaining /// controls and remove the others @@ -117,12 +76,12 @@ module DiffTests = let current = [ View.Label(text = "A") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + let x = testUpdateChildren (ValueSome previous) current + x |> should equal [ Update (0, previous.[0], current.[0]) Delete 1 Delete 2 - ]) + ] /// Keeping elements at the start (not same instance) and adding elements at the end should update the existing /// controls and add the others at the end @@ -134,11 +93,11 @@ module DiffTests = [ View.Label(text = "A") View.Label(text = "B") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Insert (1, current.[1]) - ]) + ] /// Adding a new element at the start and keeping the existing elements after (not same instances) should reuse /// the existing controls based on their position and create the missing ones @@ -152,12 +111,12 @@ module DiffTests = View.Label(text = "A") View.Label(text = "B") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Insert (2, current.[2]) - ]) + ] /// Removing elements in the middle of others (not the same instances) should reuse the existing controls based /// on their position and remove the superfluous ones @@ -173,13 +132,13 @@ module DiffTests = View.Label(text = "C") View.Label(text = "D") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) Delete 3 - ]) + ] /// Replacing an element with an element of another type should create the new control in place of the old one [] @@ -189,11 +148,11 @@ module DiffTests = let current = [ View.Button() ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Delete 0 - ]) + ] /// Adding a keyed element to an empty list should create the associated control [] @@ -201,33 +160,10 @@ module DiffTests = let previous = [] let current = [ View.Label(key = "KeyA") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) - ]) - - /// Emptying a list containing keyed elements should clear the list - [] - let ``Given previous state = 1k-2k-3k / current state = Empty, updateChildren should Clear``() = - let previous = - [ View.Label(key = "KeyA") - View.Label(key = "KeyB") - View.Label(key = "KeyC") ] - let current = - [] - - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal DiffResult.ClearCollection - - /// Keeping the exact same state (keyed + same instance) should do nothing - [] - let ``Given previous state = 1k / current state = 1k (same reference), updateChildren should do nothing``() = - let label = dependsOn () (fun _ _ -> View.Label(key = "KeyA")) - let previous = [ label ] - let current = [ label ] - - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal DiffResult.NoChange + ] /// Keeping the same state (keyed + not same instance) should update the existing control nonetheless [] @@ -235,10 +171,10 @@ module DiffTests = let previous = [ View.Label(key = "KeyA") ] let current = [ View.Label(key = "KeyA") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) - ]) + ] /// Replacing a keyed element by another one (not same key + same control type) should update the existing control [] @@ -246,11 +182,11 @@ module DiffTests = let previous = [ View.Label(key = "KeyA") ] let current = [ View.Label(key = "KeyB") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Delete 0 - ]) + ] /// Removing elements in the middle of others (not the same instances) should reuse the existing controls based /// on their keys and remove the superfluous ones @@ -264,12 +200,12 @@ module DiffTests = [ View.Label(key = "KeyA") View.Label(key = "KeyC") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) MoveAndUpdate (2, previous.[2], 1, current.[1]) Delete 1 - ]) + ] /// Reordering keyed elements should reuse the correct controls [] @@ -282,12 +218,12 @@ module DiffTests = [ View.Label(key = "KeyC") View.Label(key = "KeyA") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ MoveAndUpdate (2, previous.[2], 0, current.[0]) MoveAndUpdate (0, previous.[0], 1, current.[1]) Delete 1 - ]) + ] /// New keyed elements should reuse discarded elements even though the keys are not matching, /// independently of their position @@ -302,13 +238,13 @@ module DiffTests = View.Label(key = "KeyD") View.Label(key = "KeyA") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ MoveAndUpdate (2, previous.[2], 0, current.[0]) Insert (1, current.[1]) MoveAndUpdate (0, previous.[0], 2, current.[2]) Delete 1 - ]) + ] /// Complex use cases with reordering and remove/add of keyed elements should reuse controls efficiently [] @@ -325,13 +261,13 @@ module DiffTests = View.Label(key = "KeyD") View.Label(key = "KeyC") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ MoveAndUpdate (1, previous.[1], 0, current.[0]) Move (0, 1) MoveAndUpdate (3, previous.[3], 2, current.[2]) MoveAndUpdate (2, previous.[2], 3, current.[3]) - ]) + ] /// Replacing an element with one from another type, even with the same key, should create the new control /// in place of the old one @@ -342,11 +278,11 @@ module DiffTests = let current = [ View.Button(key = "KeyA") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Delete 0 - ]) + ] /// Replacing a keyed element with one of another type and another key, should create the new control /// in place of the old one @@ -357,11 +293,11 @@ module DiffTests = let current = [ View.Button(key = "KeyB") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Delete 0 - ]) + ] /// Replacing a keyed element with a non-keyed one should reuse the discarded element [] @@ -371,11 +307,11 @@ module DiffTests = let current = [ View.Label() ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Delete 0 - ]) + ] /// Replacing a non-keyed element with another when a keyed element is present should reuse the discarded element [] @@ -387,11 +323,11 @@ module DiffTests = [ View.Label(key = "KeyA") View.Label(text = "C") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) - ]) + ] /// Removing an element at the start of a list with keyed elements present should reuse the correct controls [] @@ -402,11 +338,11 @@ module DiffTests = let current = [ View.Label(key = "KeyB") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ MoveAndUpdate (1, previous.[1], 0, current.[0]) Delete 0 - ]) + ] /// Complex use cases with reordering and remove/add of mixed elements should reuse controls efficiently [] @@ -423,14 +359,14 @@ module DiffTests = View.Label(text = "E") View.Label(key = "KeyB") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Move (3, 0) MoveAndUpdate (0, previous.[0], 1, current.[1]) MoveAndUpdate (1, previous.[1], 2, current.[2]) Delete 2 Delete 4 - ]) + ] open Xamarin.Forms @@ -457,8 +393,8 @@ module DiffTests = View.Button(text="Reset", horizontalOptions=LayoutOptions.Center, command=(fun () -> ()), commandCanExecute = true) ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) @@ -466,7 +402,7 @@ module DiffTests = Update (4, previous.[4], current.[4]) Update (5, previous.[5], current.[5]) Update (6, previous.[6], current.[6]) - ]) + ] [] let ``Test TicTacToe``() = @@ -499,8 +435,8 @@ module DiffTests = backgroundColor=Color.LightBlue ).Row(0).Column(1) ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) @@ -508,7 +444,7 @@ module DiffTests = Insert (4, current.[4]) MoveAndUpdate (4, previous.[4], 5, current.[5]) Delete 5 - ]) + ] [] let ``Test TicTacToe 3``() = @@ -534,8 +470,8 @@ module DiffTests = View.Button(key = "Button2_1") View.Button(key = "Button2_2") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Update (0, previous.[0], current.[0]) Update (1, previous.[1], current.[1]) Update (2, previous.[2], current.[2]) @@ -546,7 +482,7 @@ module DiffTests = Update (7, previous.[7], current.[7]) Update (8, previous.[8], current.[8]) Delete 4 - ]) + ] [] let ``Test Random``() = @@ -562,8 +498,8 @@ module DiffTests = View.Button(key = "Button0_1") View.Image(key = "Button0_2") ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ Insert (0, current.[0]) Update (1, previous.[1], current.[1]) Insert (2, current.[2]) @@ -571,7 +507,7 @@ module DiffTests = Delete 2 Delete 3 Delete 4 - ]) + ] [] let ``Test Random 2``() = @@ -592,8 +528,8 @@ module DiffTests = [ View.Button(key = "0") View.Label() ] - testUpdateChildren (ValueSome previous) (ValueSome current) - |> should equal (DiffResult.Operations [ + testUpdateChildren (ValueSome previous) current + |> should equal [ MoveAndUpdate (2, previous.[2], 0, current.[0]) Update (1, previous.[1], current.[1]) @@ -609,4 +545,4 @@ module DiffTests = Delete 8 Delete 9 Delete 10 - ]) \ No newline at end of file + ] \ No newline at end of file diff --git a/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/XFOptimizer.fs b/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/XFOptimizer.fs index a44ae0dde..6e3820815 100644 --- a/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/XFOptimizer.fs +++ b/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/XFOptimizer.fs @@ -79,6 +79,58 @@ module XFOptimizer = ConvertModelToValue = "ViewConverters.convertFabulousMediaToXamarinFormsMediaSource" } let apply = Optimizer.propertyOptimizer (fun _ prop -> canBeOptimized prop) (fun _ prop -> [| optimizeBoundProperty prop |]) + + /// Optimize Color properties to use memoization + module OptimizeColor = + let private canBeOptimized (boundProperty: BoundProperty) = + boundProperty.InputType = "Xamarin.Forms.Color" + && boundProperty.ConvertInputToModel = "" + && boundProperty.ConvertModelToValue = "" + && boundProperty.UpdateCode = "" + && boundProperty.ModelType = boundProperty.InputType + + let private optimizeBoundProperty (boundProperty: BoundProperty) = + { boundProperty with + ModelType = "obj" + ConvertInputToModel = "Fabulous.XamarinForms.ViewConverters.convertXamarinFormsColor" + ConvertModelToValue = "unbox" } + + let apply = Optimizer.propertyOptimizer (fun _ prop -> canBeOptimized prop) (fun _ prop -> [| optimizeBoundProperty prop |]) + + /// Optimize Thickness properties to use memoization + module OptimizeThickness = + let private canBeOptimized (boundProperty: BoundProperty) = + boundProperty.InputType = "Xamarin.Forms.Thickness" + && boundProperty.ConvertInputToModel = "" + && boundProperty.ConvertModelToValue = "" + && boundProperty.UpdateCode = "" + && boundProperty.ModelType = boundProperty.InputType + + let private optimizeBoundProperty (boundProperty: BoundProperty) = + { boundProperty with + ModelType = "obj" + ConvertInputToModel = "Fabulous.XamarinForms.ViewConverters.convertXamarinFormsThickness" + ConvertModelToValue = "unbox" } + + let apply = Optimizer.propertyOptimizer (fun _ prop -> canBeOptimized prop) (fun _ prop -> [| optimizeBoundProperty prop |]) + + /// Optimize LayoutOptions properties to use memoization + module OptimizeLayoutOptions = + let private canBeOptimized (boundProperty: BoundProperty) = + boundProperty.InputType = "Xamarin.Forms.LayoutOptions" + && boundProperty.ConvertInputToModel = "" + && boundProperty.ConvertModelToValue = "" + && boundProperty.UpdateCode = "" + && boundProperty.ModelType = boundProperty.InputType + + let private optimizeBoundProperty (boundProperty: BoundProperty) = + { boundProperty with + ModelType = "obj" + ConvertInputToModel = "Fabulous.XamarinForms.ViewConverters.convertXamarinFormsLayoutOptions" + ConvertModelToValue = "unbox" } + + let apply = Optimizer.propertyOptimizer (fun _ prop -> canBeOptimized prop) (fun _ prop -> [| optimizeBoundProperty prop |]) + let optimize = let xfOptimize boundModel = @@ -86,6 +138,9 @@ module XFOptimizer = |> OptimizeCommands.apply |> OptimizeImageSource.apply |> OptimizeMediaSource.apply + |> OptimizeColor.apply + |> OptimizeThickness.apply + |> OptimizeLayoutOptions.apply Optimizer.optimize >> WorkflowResult.map xfOptimize diff --git a/Fabulous.sln b/Fabulous.sln index b1acb6137..c8a8dd624 100644 --- a/Fabulous.sln +++ b/Fabulous.sln @@ -179,7 +179,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoginShape.UWP", "Fabulous. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ShapesDemo", "ShapesDemo", "{09E8890D-BEA5-4F3A-AE32-B5DE292783D4}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ShapesDemo", "Fabulous.XamarinForms\samples\ShapesDemo\ShapesDemo\ShapesDemo.fsproj", "{AA2BEC0E-9BA3-4103-A60E-1B61F0F83CE6}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ShapesDemo", "Fabulous.XamarinForms\samples\ShapesDemo\ShapesDemo\ShapesDemo.fsproj", "{AA2BEC0E-9BA3-4103-A60E-1B61F0F83CE6}" EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ShapesDemo.iOS", "Fabulous.XamarinForms\samples\ShapesDemo\iOS\ShapesDemo.iOS.fsproj", "{B7153CCA-BAE3-4571-A69F-79275DEAF88C}" EndProject @@ -189,7 +189,14 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ShapesDemo.macOS", "Fabulou EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShapesDemo.UWP", "Fabulous.XamarinForms\samples\ShapesDemo\UWP\ShapesDemo.UWP.csproj", "{D844F7AD-2860-4D92-8D53-B0017D62FC1F}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ShapesDemo.WPF", "Fabulous.XamarinForms\samples\ShapesDemo\WPF\ShapesDemo.WPF.fsproj", "{BD6B48FC-CE37-4AD3-9EC6-8BBCDE18B7C7}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ShapesDemo.WPF", "Fabulous.XamarinForms\samples\ShapesDemo\WPF\ShapesDemo.WPF.fsproj", "{BD6B48FC-CE37-4AD3-9EC6-8BBCDE18B7C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FDE2AAC3-9813-4656-968F-54F49484E7A7}" + ProjectSection(SolutionItems) = preProject + Packages.targets = Packages.targets + paket.dependencies = paket.dependencies + RELEASE_NOTES.md = RELEASE_NOTES.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Packages.targets b/Packages.targets index d36bfea51..a39550827 100644 --- a/Packages.targets +++ b/Packages.targets @@ -10,6 +10,7 @@ + diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a81f1bc60..987714a99 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,9 @@ -#### 0.58.0 +#### 0.60.0-preview1 -* [All] Proper version constraints for the NuGet packages +* [All] Proper version constraints for the NuGet packages (https://github.com/fsprojects/Fabulous/pull/797) +* [All] Add FSharp.Core as a public dependency (https://github.com/fsprojects/Fabulous/pull/796) +* [All] Reduced allocations (https://github.com/fsprojects/Fabulous/pull/805) +* [Fabulous.XamarinForms] [Templates] Add native main menu to MacOS template (https://github.com/fsprojects/Fabulous/pull/806) #### 0.57.0 diff --git a/build.fsx b/build.fsx index 0d3540a88..05f31fc1f 100644 --- a/build.fsx +++ b/build.fsx @@ -46,11 +46,9 @@ let addJDK properties = let dotnetBuild outputSubDir paths = for projectPath in paths do - let outputPath = composeOutputPath outputSubDir projectPath DotNet.build (fun opt -> { opt with - Configuration = DotNet.BuildConfiguration.Release - OutputPath = Some outputPath }) projectPath + Configuration = DotNet.BuildConfiguration.Release }) projectPath let computeBounds (semVer: SemVerInfo) = match semVer.PreRelease with @@ -82,12 +80,11 @@ let dotnetTest outputSubDir paths = Logger = Some "trx" ResultsDirectory = Some outputPath }) projectPath -let msbuild outputSubDir paths = +let msbuild paths = for projectPath in paths do - let outputPath = composeOutputPath outputSubDir projectPath let projectName = Path.GetFileNameWithoutExtension projectPath let properties = [ ("Configuration", "Release") ] |> addJDK - MSBuild.run id outputPath "Build" properties [projectPath] |> Trace.logItems (projectName + "-Build-Output: ") + MSBuild.run id "" "Build" properties [projectPath] |> Trace.logItems (projectName + "-Build-Output: ") let nugetPack paths = for nuspecPath in paths do @@ -197,7 +194,7 @@ Target.create "BuildFabulousXamarinFormsDependencies" (fun _ -> ) Target.create "RunGeneratorForFabulousXamarinForms" (fun _ -> - let generatorPath = buildDir + "/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/Fabulous.XamarinForms.Generator.dll" + let generatorPath = "Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/bin/Release/netcoreapp3.1/Fabulous.XamarinForms.Generator.dll" let mappingFilePath = "Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.json" let outputFilePath = "Fabulous.XamarinForms/src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs" @@ -220,7 +217,7 @@ Target.create "RunFabulousXamarinFormsTests" (fun _ -> ) Target.create "RunGeneratorForFabulousXamarinFormsExtensions" (fun _ -> - let generatorPath = buildDir + "/Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/Fabulous.XamarinForms.Generator.dll" + let generatorPath = "Fabulous.XamarinForms/tools/Fabulous.XamarinForms.Generator/bin/Release/netcoreapp3.1/Fabulous.XamarinForms.Generator.dll" for mappingFile in !!"Fabulous.XamarinForms/extensions/**/*.json" do let outputFile = mappingFile.Replace(".json", ".fs") @@ -284,7 +281,7 @@ Target.create "PackFabulousXamarinFormsExtensions" (fun _ -> Target.create "BuildFabulousXamarinFormsSamples" (fun _ -> !! "Fabulous.XamarinForms/samples/**/*.fsproj" |> removeIncompatiblePlatformProjects - |> msbuild "Fabulous.XamarinForms/samples" + |> msbuild ) Target.create "RunFabulousXamarinFormsSamplesTests" (fun _ -> @@ -295,7 +292,7 @@ Target.create "RunFabulousXamarinFormsSamplesTests" (fun _ -> Target.create "BuildFabulousStaticViewSamples" (fun _ -> !! "Fabulous.StaticView/samples/**/*.fsproj" |> removeIncompatiblePlatformProjects - |> msbuild "Fabulous.StaticView/samples" + |> msbuild ) Target.create "TestTemplatesNuGet" (fun _ -> diff --git a/src/Fabulous/ViewElement.fs b/src/Fabulous/ViewElement.fs index c3df76f33..a1edefc80 100644 --- a/src/Fabulous/ViewElement.fs +++ b/src/Fabulous/ViewElement.fs @@ -110,19 +110,33 @@ type ViewRef<'T when 'T : not struct>() = | _ -> None /// A description of a visual element -type ViewElement internal (targetType: Type, create: (unit -> obj), update: (ViewElement voption -> ViewElement -> obj -> unit), attribs: KeyValuePair[]) = +type ViewElement internal (targetType: Type, create: (unit -> obj), update: (ViewElement voption -> ViewElement -> obj -> unit), updateAttachedProperties: (int -> ViewElement voption -> ViewElement -> obj -> unit), attribs: KeyValuePair[]) = - new (targetType: Type, create: (unit -> obj), update: (ViewElement voption -> ViewElement -> obj -> unit), attribsBuilder: AttributesBuilder) = - ViewElement(targetType, create, update, attribsBuilder.Close()) + // Recursive search of an attribute by its key. + // Perf note: This is preferred to Array.tryFind because it avoids capturing the context with a lambda + let tryFindAttrib key = + let rec tryFindAttribRec key i = + if i >= attribs.Length then + ValueNone + elif attribs.[i].Key = key then + ValueSome (attribs.[i]) + else + tryFindAttribRec key (i + 1) + tryFindAttribRec key 0 + + new (targetType: Type, create: (unit -> obj), update: (ViewElement voption -> ViewElement -> obj -> unit), updateAttachedProperties: (int -> ViewElement voption -> ViewElement -> obj -> unit), attribsBuilder: AttributesBuilder) = + ViewElement(targetType, create, update, updateAttachedProperties, attribsBuilder.Close()) static member Create (create: (unit -> 'T), - update: (Dictionary ViewElement -> obj -> unit> -> ViewElement voption -> ViewElement -> 'T -> unit), + update: (ViewElement voption -> ViewElement -> 'T -> unit), + updateAttachedProperties: (int -> ViewElement voption -> ViewElement -> obj -> unit), attribsBuilder: AttributesBuilder) = ViewElement( typeof<'T>, (create >> box), - (fun prev curr target -> update (Dictionary()) prev curr (unbox target)), + (fun prev curr target -> update prev curr (unbox target)), + updateAttachedProperties, attribsBuilder.Close() ) @@ -140,13 +154,12 @@ type ViewElement internal (targetType: Type, create: (unit -> obj), update: (Vie /// Get the attributes of the visual element [] member x.Attributes = attribs |> Array.map (fun kvp -> KeyValuePair(AttributeKey.GetName kvp.Key, kvp.Value)) - /// Get an attribute of the visual element member x.TryGetAttributeKeyed<'T>(key: AttributeKey<'T>) = - match attribs |> Array.tryFind (fun kvp -> kvp.Key = key.KeyValue) with - | Some kvp -> ValueSome(unbox<'T>(kvp.Value)) - | None -> ValueNone + match tryFindAttrib key.KeyValue with + | ValueSome kvp -> ValueSome(unbox<'T>(kvp.Value)) + | ValueNone -> ValueNone /// Get an attribute of the visual element member x.TryGetAttribute<'T>(name: string) = @@ -154,9 +167,9 @@ type ViewElement internal (targetType: Type, create: (unit -> obj), update: (Vie /// Get an attribute of the visual element member x.GetAttributeKeyed<'T>(key: AttributeKey<'T>) = - match attribs |> Array.tryFind (fun kvp -> kvp.Key = key.KeyValue) with - | Some kvp -> unbox<'T>(kvp.Value) - | None -> failwithf "Property '%s' does not exist on %s" key.Name x.TargetType.Name + match tryFindAttrib key.KeyValue with + | ValueSome kvp -> unbox<'T>(kvp.Value) + | ValueNone -> failwithf "Property '%s' does not exist on %s" key.Name x.TargetType.Name /// Try get the key attribute value member x.TryGetKey() = x.TryGetAttributeKeyed(ViewElement.KeyAttribKey) @@ -170,6 +183,9 @@ type ViewElement internal (targetType: Type, create: (unit -> obj), update: (Vie /// Differentially update the inherited attributes of a visual element given the previous settings member x.UpdateInherited(prevOpt: ViewElement voption, curr: ViewElement, target: obj) = update prevOpt curr target + /// Differentially update the attached properties of a child of a collection + member x.UpdateAttachedPropertiesForAttribute<'T>(attributeKey: AttributeKey<'T>, prevOpt: ViewElement voption, curr: ViewElement, target: obj) = updateAttachedProperties attributeKey.KeyValue prevOpt curr target + /// Create the UI element from the view description member x.Create() : obj = Debug.WriteLine (sprintf "Create %O" x.TargetType) @@ -189,7 +205,7 @@ type ViewElement internal (targetType: Type, create: (unit -> obj), update: (Vie let attribs2 = Array.zeroCreate newAttribsLength Array.blit attribs 0 attribs2 0 attribs.Length attribs2.[attribIndex] <- KeyValuePair(key.KeyValue, box value) - ViewElement(targetType, create, update, attribs2) + ViewElement(targetType, create, update, updateAttachedProperties, attribs2) let n = attribs.Length diff --git a/src/Fabulous/ViewHelpers.fs b/src/Fabulous/ViewHelpers.fs index 1ae5f827e..e0cbf156b 100644 --- a/src/Fabulous/ViewHelpers.fs +++ b/src/Fabulous/ViewHelpers.fs @@ -14,6 +14,25 @@ module SimplerHelpers = Memoizations.T.Clear() Memoizations.T.[key] <- System.WeakReference(box res) + /// Because ViewElement stores its values as KeyValuePair, + /// Structs like Xamarin.Forms.Color/Thickness will be boxed each time we use them + /// To prevent that, we memoize the boxed value for the recently used structs + type StructMemoizations<'T when 'T : struct and 'T : equality>() = + static let t = Dictionary() + static member T = t + static member Add(res: 'T) = + if StructMemoizations<'T>.T.Count > 100 then + System.Diagnostics.Trace.WriteLine(sprintf "Clearing %s memoizations..." typeof<'T>.FullName) + StructMemoizations<'T>.T.Clear() + let key = res.GetHashCode() + let value = box res + StructMemoizations<'T>.T.[key] <- value + value + static member TryGetValue(res: 'T) = + match StructMemoizations<'T>.T.TryGetValue(res.GetHashCode()) with + | false, _ -> ValueNone + | true, value -> ValueSome value + type DoNotUseModelInsideDependsOn = | DoNotUseModelInsideDependsOn /// Memoize part of a view model computation. Also prevent the use of the model inside