diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 81ed67865..79f899d7b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,11 @@ "isRoot": true, "tools": { "fantomas": { - "version": "6.2.3", + "version": "6.3.15", "commands": [ "fantomas" - ] + ], + "rollForward": false } } } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 157eaa5d6..faf65337f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 _No unreleased changes_ +## [3.0.0-pre6] - 2024-09-24 + +### Changed +- Add equality constraint on 'msg to prevent confusion with new non-MVU modifiers by @TimLariviere https://github.com/fabulous-dev/Fabulous/pull/1084 + +### Added +- Add new `CollectionBuilder` constructor using an `AttributesBundle` by @edgarfgp in https://github.com/fabulous-dev/Fabulous/pull/1081 + ## [3.0.0-pre5] - 2024-05-17 ### Added @@ -139,7 +147,8 @@ _No unreleased changes_ ### Changed - Fabulous.XamarinForms & Fabulous.MauiControls have been moved been out of the Fabulous repository. Find them in their own repositories: [https://github.com/fabulous-dev/Fabulous.XamarinForms](https://github.com/fabulous-dev/Fabulous.XamarinForms) / [https://github.com/fabulous-dev/Fabulous.MauiControls](https://github.com/fabulous-dev/Fabulous.MauiControls) -[unreleased]: https://github.com/fabulous-dev/Fabulous/compare/3.0.0-pre5...HEAD +[unreleased]: https://github.com/fabulous-dev/Fabulous/compare/3.0.0-pre6...HEAD +[3.0.0-pre6]: https://github.com/fabulous-dev/Fabulous/releases/tag/3.0.0-pre6 [3.0.0-pre5]: https://github.com/fabulous-dev/Fabulous/releases/tag/3.0.0-pre5 [3.0.0-pre4]: https://github.com/fabulous-dev/Fabulous/releases/tag/3.0.0-pre4 [3.0.0-pre3]: https://github.com/fabulous-dev/Fabulous/releases/tag/3.0.0-pre3 diff --git a/src/Fabulous.Tests/APISketchTests/APISketchTests.fs b/src/Fabulous.Tests/APISketchTests/APISketchTests.fs index 733bb4571..aad48e406 100644 --- a/src/Fabulous.Tests/APISketchTests/APISketchTests.fs +++ b/src/Fabulous.Tests/APISketchTests/APISketchTests.fs @@ -642,29 +642,29 @@ module Issue104 = let ControlWidgetKey = Widgets.register() - let Control<'msg> () = + let Control () = WidgetBuilder<'msg, TestButtonMarker>(ControlWidgetKey, AttributesBundle(StackList.StackList.empty(), ValueNone, ValueNone)) [] type WidgetExtensions() = [] - static member inline attr1<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline attr1<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attr1.WithValue(value)) [] - static member inline attr2<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline attr2<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attr2.WithValue(value)) [] - static member inline attr3<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline attr3<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attr3.WithValue(value)) [] - static member inline attr4<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline attr4<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attr4.WithValue(value)) [] - static member inline attr5<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline attr5<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attr5.WithValue(value)) let view model = diff --git a/src/Fabulous.Tests/APISketchTests/TestUI.Widgets.fs b/src/Fabulous.Tests/APISketchTests/TestUI.Widgets.fs index be031185c..bde5e6beb 100644 --- a/src/Fabulous.Tests/APISketchTests/TestUI.Widgets.fs +++ b/src/Fabulous.Tests/APISketchTests/TestUI.Widgets.fs @@ -59,9 +59,7 @@ module TestUI_Widgets = //-----MARKERS----------- - type IMarker = - interface - end + type IMarker = interface end type TextMarker = inherit IMarker @@ -84,39 +82,37 @@ module TestUI_Widgets = [] type WidgetExtensions() = [] - static member inline automationId<'msg, 'marker when 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline automationId<'msg, 'marker when 'msg: equality and 'marker :> IMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attributes.Automation.AutomationId.WithValue(value)) [] - static member inline automationId<'msg, 'marker, 'itemMarker when 'marker :> IMarker> - ( - this: CollectionBuilder<'msg, 'marker, 'itemMarker>, - value: string - ) = + static member inline automationId<'msg, 'marker, 'itemMarker when 'msg: equality and 'marker :> IMarker> + (this: CollectionBuilder<'msg, 'marker, 'itemMarker>, value: string) + = this.AddScalar(Attributes.Automation.AutomationId.WithValue(value)) [] - static member inline textColor<'msg, 'marker when 'marker :> TextMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = + static member inline textColor<'msg, 'marker when 'msg: equality and 'marker :> TextMarker>(this: WidgetBuilder<'msg, 'marker>, value: string) = this.AddScalar(Attributes.TextStyle.TextColor.WithValue(value)) [] - static member inline record<'msg, 'marker when 'marker :> TestLabelMarker>(this: WidgetBuilder<'msg, 'marker>, value: bool) = + static member inline record<'msg, 'marker when 'msg: equality and 'marker :> TestLabelMarker>(this: WidgetBuilder<'msg, 'marker>, value: bool) = this.AddScalar(Attributes.Text.Record.WithValue(value)) [] - static member inline tap<'msg, 'marker when 'marker :> TestButtonMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = + static member inline tap<'msg, 'marker when 'msg: equality and 'marker :> TestButtonMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = this.AddScalar(Attributes.Button.Tap.WithValue(value)) [] - static member inline tap2<'msg, 'marker when 'marker :> TestButtonMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = + static member inline tap2<'msg, 'marker when 'msg: equality and 'marker :> TestButtonMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = this.AddScalar(Attributes.Button.Tap2.WithValue(value)) [] - static member inline tapContainer<'msg, 'marker when 'marker :> TestStackMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = + static member inline tapContainer<'msg, 'marker when 'msg: equality and 'marker :> TestStackMarker>(this: WidgetBuilder<'msg, 'marker>, value: 'msg) = this.AddScalar(Attributes.Container.Tap.WithValue(value)) ///---------------- @@ -129,14 +125,14 @@ module TestUI_Widgets = static let TestStackKey = Widgets.register() static let TestNumericBagKey = Widgets.register() - static member Label<'msg>(text: string) = + static member Label(text: string) = WidgetBuilder<'msg, TestLabelMarker>(TestLabelKey, Attributes.Text.Text.WithValue(text)) - static member Button<'msg>(text: string, onClicked: 'msg) = + static member Button(text: string, onClicked: 'msg) = WidgetBuilder<'msg, TestButtonMarker>(TestButtonKey, Attributes.Text.Text.WithValue(text), Attributes.Button.Pressed.WithValue(onClicked)) - static member BoxedNumericBag<'msg>(one, two, three) = + static member BoxedNumericBag(one, two, three) = WidgetBuilder<'msg, TestNumericBagMarker>( TestNumericBagKey, Attributes.NumericBag.BoxedValueOne.WithValue(one), @@ -144,7 +140,7 @@ module TestUI_Widgets = Attributes.NumericBag.BoxedValueThree.WithValue(three) ) - static member InlineNumericBag<'msg>(one, two, three) = + static member InlineNumericBag(one, two, three) = WidgetBuilder<'msg, TestNumericBagMarker>( TestNumericBagKey, Attributes.NumericBag.InlineValueOne.WithValue(one, (fun x -> x)), @@ -154,33 +150,27 @@ module TestUI_Widgets = Attributes.NumericBag.InlineValueThree.WithValue(three, BitConverter.DoubleToUInt64Bits) ) - static member Stack<'msg, 'marker when 'marker :> IMarker>() = + static member Stack<'msg, 'marker when 'msg: equality and 'marker :> IMarker>() = CollectionBuilder<'msg, TestStackMarker, 'marker>(TestStackKey, StackList.empty(), Attributes.Container.Children) [] type CollectionBuilderExtensions = [] - static member inline Yield<'msg, 'marker, 'itemMarker when 'itemMarker :> IMarker> - ( - _: CollectionBuilder<'msg, 'marker, IMarker>, - x: WidgetBuilder<'msg, 'itemMarker> - ) : Content<'msg> = + static member inline Yield<'msg, 'marker, 'itemMarker when 'msg: equality and 'itemMarker :> IMarker> + (_: CollectionBuilder<'msg, 'marker, IMarker>, x: WidgetBuilder<'msg, 'itemMarker>) + : Content<'msg> = CollectionBuilder.yieldImpl x [] - static member inline Yield<'msg, 'marker, 'itemMarker when 'itemMarker :> IMarker> - ( - _: CollectionBuilder<'msg, 'marker, IMarker>, - x: WidgetBuilder<'msg, Memo.Memoized<'itemMarker>> - ) : Content<'msg> = + static member inline Yield<'msg, 'marker, 'itemMarker when 'msg: equality and 'itemMarker :> IMarker> + (_: CollectionBuilder<'msg, 'marker, IMarker>, x: WidgetBuilder<'msg, Memo.Memoized<'itemMarker>>) + : Content<'msg> = CollectionBuilder.yieldImpl x [] - static member inline YieldFrom<'msg, 'marker, 'itemMarker when 'itemMarker :> IMarker> - ( - _: CollectionBuilder<'msg, 'marker, IMarker>, - x: WidgetBuilder<'msg, 'itemMarker> seq - ) : Content<'msg> = + static member inline YieldFrom<'msg, 'marker, 'itemMarker when 'msg: equality and 'itemMarker :> IMarker> + (_: CollectionBuilder<'msg, 'marker, IMarker>, x: WidgetBuilder<'msg, 'itemMarker> seq) + : Content<'msg> = // TODO optimize this one with addMut { Widgets = x |> Seq.map(fun wb -> wb.Compile()) |> Seq.toArray |> MutStackArray1.fromArray } @@ -188,7 +178,7 @@ module TestUI_Widgets = ///------------------ - type StatefulView<'arg, 'model, 'msg, 'marker> = + type StatefulView<'arg, 'model, 'msg, 'marker when 'msg: equality> = { Init: 'arg -> 'model Update: 'msg -> 'model -> 'model View: 'model -> WidgetBuilder<'msg, 'marker> } @@ -201,7 +191,7 @@ module TestUI_Widgets = module Run = - type Instance<'arg, 'model, 'msg, 'marker>(program: StatefulView<'arg, 'model, 'msg, 'marker>) = + type Instance<'arg, 'model, 'msg, 'marker when 'msg: equality>(program: StatefulView<'arg, 'model, 'msg, 'marker>) = let mutable state: ('model * obj * Widget) option = None member private x.viewContext: ViewTreeContext = diff --git a/src/Fabulous.Tests/ViewTests.fs b/src/Fabulous.Tests/ViewTests.fs index 68943493b..bcdfcdbed 100644 --- a/src/Fabulous.Tests/ViewTests.fs +++ b/src/Fabulous.Tests/ViewTests.fs @@ -4,9 +4,7 @@ open Fabulous open Fabulous.StackAllocatedCollections.StackList open NUnit.Framework -type ITestControl = - interface - end +type ITestControl = interface end module TestControl = let WidgetKey: WidgetKey = 1 diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index 908301327..4a5b1cc51 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -31,10 +31,8 @@ module ScalarAttributeDefinitions = Value = null } static member inline CreateAttributeData<'T> - ( - [] decode: uint64 -> 'T, - [] updateNode: 'T voption -> 'T voption -> IViewNode -> unit - ) : SmallScalarAttributeData = + ([] decode: uint64 -> 'T, [] updateNode: 'T voption -> 'T voption -> IViewNode -> unit) + : SmallScalarAttributeData = { UpdateNode = (fun oldValueOpt newValueOpt node -> let oldValueOpt = @@ -64,10 +62,8 @@ module ScalarAttributeDefinitions = Value = value } static member CreateAttributeData - ( - compare: 'T -> 'T -> ScalarAttributeComparison, - updateNode: 'T voption -> 'T voption -> IViewNode -> unit - ) : ScalarAttributeData = + (compare: 'T -> 'T -> ScalarAttributeComparison, updateNode: 'T voption -> 'T voption -> IViewNode -> unit) + : ScalarAttributeData = { CompareBoxed = (fun a b -> compare (unbox<'T> a) (unbox<'T> b)) UpdateNode = (fun oldValueOpt newValueOpt node -> diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index a272fe763..dd1c5d1ba 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -78,10 +78,8 @@ type SmallScalarExtensions() = [] static member inline WithValue< ^T when ^T: enum and ^T: (static member op_Explicit: ^T -> uint64)> - ( - this: SmallScalarAttributeDefinition< ^T >, - value - ) = + (this: SmallScalarAttributeDefinition< ^T >, value) + = this.WithValue(value, SmallScalars.IntEnum.encode) type MsgValue = MsgValue of obj diff --git a/src/Fabulous/Builders.fs b/src/Fabulous/Builders.fs index 68df45e17..712749f78 100644 --- a/src/Fabulous/Builders.fs +++ b/src/Fabulous/Builders.fs @@ -10,7 +10,7 @@ open Microsoft.FSharp.Core type AttributesBundle = (struct (StackList * WidgetAttribute[] voption * WidgetCollectionAttribute[] voption)) [] -type WidgetBuilder<'msg, 'marker> = +type WidgetBuilder<'msg, 'marker when 'msg: equality> = struct val Key: WidgetKey val Attributes: AttributesBundle @@ -122,7 +122,7 @@ type WidgetBuilder<'msg, 'marker> = type Content<'msg> = { Widgets: MutStackArray1.T } [] -type CollectionBuilder<'msg, 'marker, 'itemMarker> = +type CollectionBuilder<'msg, 'marker, 'itemMarker when 'msg: equality> = struct val WidgetKey: WidgetKey val Attr: WidgetCollectionAttributeDefinition @@ -207,7 +207,7 @@ module CollectionBuilder = { Widgets = MutStackArray1.One(builder.Compile()) } [] -type AttributeCollectionBuilder<'msg, 'marker, 'itemMarker> = +type AttributeCollectionBuilder<'msg, 'marker, 'itemMarker when 'msg: equality> = struct val Widget: WidgetBuilder<'msg, 'marker> val Attr: WidgetCollectionAttributeDefinition @@ -240,10 +240,10 @@ type AttributeCollectionBuilder<'msg, 'marker, 'itemMarker> = res end -type SingleChildBuilderStep<'msg, 'marker> = delegate of unit -> WidgetBuilder<'msg, 'marker> +type SingleChildBuilderStep<'msg, 'marker when 'msg: equality> = delegate of unit -> WidgetBuilder<'msg, 'marker> [] -type SingleChildBuilder<'msg, 'marker, 'childMarker> = +type SingleChildBuilder<'msg, 'marker, 'childMarker when 'msg: equality> = val WidgetKey: WidgetKey val Attr: WidgetAttributeDefinition val AttributesBundle: AttributesBundle @@ -262,10 +262,8 @@ type SingleChildBuilder<'msg, 'marker, 'childMarker> = SingleChildBuilderStep(fun () -> widget) member inline this.Combine - ( - [] a: SingleChildBuilderStep<'msg, 'childMarker>, - [] _b: SingleChildBuilderStep<'msg, 'childMarker> - ) = + ([] a: SingleChildBuilderStep<'msg, 'childMarker>, [] _b: SingleChildBuilderStep<'msg, 'childMarker>) + = SingleChildBuilderStep(fun () -> // We only want one child, so we ignore the second one a.Invoke()) diff --git a/src/Fabulous/Component.fs b/src/Fabulous/Component.fs index e16382c71..48ec99459 100644 --- a/src/Fabulous/Component.fs +++ b/src/Fabulous/Component.fs @@ -207,7 +207,7 @@ type binding type ComponentBody = delegate of ComponentContext -> struct (ComponentContext * Widget) [] -type ComponentData = { Body: ComponentBody } +type ComponentData = { Key: string; Body: ComponentBody } type Component(treeContext: ViewTreeContext, body: ComponentBody, context: ComponentContext) = let mutable _body = body @@ -302,19 +302,22 @@ type Component(treeContext: ViewTreeContext, body: ComponentBody, context: Compo node member private this.RenderInternal() = - let prevRootWidget = _widget - let prevContext = _context - let struct (context, currRootWidget) = _body.Invoke(_context) - _widget <- currRootWidget + if _body = null then + () // Component has been disposed + else + let prevRootWidget = _widget + let prevContext = _context + let struct (context, currRootWidget) = _body.Invoke(_context) + _widget <- currRootWidget - if prevContext <> context then - _contextSubscription.Dispose() - _contextSubscription <- context.RenderNeeded.Subscribe(this.Render) - _context <- context + if prevContext <> context then + _contextSubscription.Dispose() + _contextSubscription <- context.RenderNeeded.Subscribe(this.Render) + _context <- context - let viewNode = treeContext.GetViewNode _view + let viewNode = treeContext.GetViewNode _view - Reconciler.update treeContext.CanReuseView (ValueSome prevRootWidget) currRootWidget viewNode + Reconciler.update treeContext.CanReuseView (ValueSome prevRootWidget) currRootWidget viewNode member this.Dispose() = if _contextSubscription <> null then @@ -333,7 +336,10 @@ type Component(treeContext: ViewTreeContext, body: ComponentBody, context: Compo member this.Dispose() = this.Dispose() member this.Render(_) = - treeContext.SyncAction(this.RenderInternal) + if _body = null then + () // Component has been disposed + else + treeContext.SyncAction(this.RenderInternal) module Component = let Data = @@ -390,11 +396,42 @@ module Component = WidgetDefinitionStore.set key definition key + let canReuseComponent (prev: Widget) (curr: Widget) = + let prevData = + match prev.ScalarAttributes with + | ValueSome attrs -> + let scalarAttrsOpt = + attrs |> Array.tryFind(fun scalarAttr -> scalarAttr.Key = Data.Key) + + match scalarAttrsOpt with + | None -> failwithf "Component widget must have a body" + | Some value -> value.Value :?> ComponentData + + | _ -> failwith "Component widget must have a body" + + let currData = + match curr.ScalarAttributes with + | ValueSome attrs -> + let scalarAttrsOpt = + attrs |> Array.tryFind(fun scalarAttr -> scalarAttr.Key = Data.Key) + + match scalarAttrsOpt with + | None -> failwithf "Component widget must have a body" + | Some value -> value.Value :?> ComponentData + | _ -> failwith "Component widget must have a body" + + // NOTE: Somehow using = here crashes the app and prevents debugging... + Object.Equals(prevData.Key, currData.Key) + /// Delegate used by the ComponentBuilder to compose a component body /// It will be aggressively inlined by the compiler leaving no overhead, only a pure function that returns a WidgetBuilder type ComponentBodyBuilder<'marker> = delegate of bindings: int * context: ComponentContext -> struct (int * WidgetBuilder) -type ComponentBuilder<'parentMsg>() = +type ComponentBuilder<'parentMsg when 'parentMsg: equality> = + val public Key: string + + new(key: string) = { Key = key } + member inline this.Yield(widgetBuilder: WidgetBuilder) = ComponentBodyBuilder<'marker>(fun bindings ctx -> struct (bindings, widgetBuilder)) @@ -419,6 +456,6 @@ type ComponentBuilder<'parentMsg>() = let struct (_, result) = body.Invoke(0, ctx) struct (ctx, result.Compile())) - let data = { Body = compiledBody } + let data = { Key = this.Key; Body = compiledBody } WidgetBuilder<'parentMsg, 'marker>(Component.WidgetKey, Component.Data.WithValue(data)) diff --git a/src/Fabulous/ComponentContext.fs b/src/Fabulous/ComponentContext.fs index 558b0fa7c..5be44b431 100644 --- a/src/Fabulous/ComponentContext.fs +++ b/src/Fabulous/ComponentContext.fs @@ -86,6 +86,4 @@ type ComponentContext(initialSize: int) = member this.Dispose() = this.Dispose() [] -type Context private () = - class - end +type Context private () = class end diff --git a/src/Fabulous/MvuComponent.fs b/src/Fabulous/MvuComponent.fs index 5089159b0..2b66148d6 100644 --- a/src/Fabulous/MvuComponent.fs +++ b/src/Fabulous/MvuComponent.fs @@ -5,7 +5,8 @@ open System.Runtime.CompilerServices [] type MvuComponentData = - { Body: ComponentBody + { Key: String + Body: ComponentBody Program: Program Arg: obj } @@ -121,19 +122,21 @@ module MvuComponent = | _ -> failwith "Component widget must have a body" // NOTE: Somehow using = here crashes the app and prevents debugging... - Object.Equals(prevData.Arg, currData.Arg) + Object.Equals(prevData.Key, currData.Key) + && Object.Equals(prevData.Arg, currData.Arg) /// Delegate used by the MvuComponentBuilder to compose a component body /// It will be aggressively inlined by the compiler leaving no overhead, only a pure function that returns a WidgetBuilder -type MvuComponentBodyBuilder<'msg, 'marker> = +type MvuComponentBodyBuilder<'msg, 'marker when 'msg: equality> = delegate of bindings: int * context: ComponentContext -> struct (int * WidgetBuilder<'msg, 'marker>) [] -type MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg> = +type MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg when 'msg: equality and 'parentMsg: equality> = + val public Key: string val public Program: Program val public Arg: obj - new(program: Program<'arg, 'model, 'msg>, arg: 'arg) = + new(key: string, program: Program<'arg, 'model, 'msg>, arg: 'arg) = let program: Program = { Init = fun arg -> let model, cmd = program.Init(unbox arg) in (box model, Cmd.map box cmd) Update = fun (msg, model) -> let model, cmd = program.Update(unbox msg, unbox model) in (box model, Cmd.map box cmd) @@ -141,7 +144,9 @@ type MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg> = Logger = program.Logger ExceptionHandler = program.ExceptionHandler } - { Program = program; Arg = arg } + { Key = key + Program = program + Arg = arg } member inline this.Yield(widgetBuilder: WidgetBuilder<'msg, 'marker>) = MvuComponentBodyBuilder<'msg, 'marker>(fun bindings ctx -> struct (bindings, widgetBuilder)) @@ -168,15 +173,14 @@ type MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg> = struct (ctx, result.Compile())) let data = - { Program = this.Program + { Key = this.Key + Program = this.Program Arg = this.Arg Body = compiledBody } WidgetBuilder<'parentMsg, 'marker>(MvuComponent.WidgetKey, MvuComponent.Data.WithValue(data)) -type MvuStateRequest = - struct - end +type MvuStateRequest = struct end type Mvu = static member inline State = MvuStateRequest() diff --git a/src/Fabulous/Program.fs b/src/Fabulous/Program.fs index 4b0f4ad78..2f8654870 100644 --- a/src/Fabulous/Program.fs +++ b/src/Fabulous/Program.fs @@ -19,7 +19,7 @@ type Program<'arg, 'model, 'msg> = ExceptionHandler: exn -> bool } -type Program<'arg, 'model, 'msg, 'marker> = +type Program<'arg, 'model, 'msg, 'marker when 'msg: equality> = { State: Program<'arg, 'model, 'msg> /// Render the application state diff --git a/src/Fabulous/View.fs b/src/Fabulous/View.fs index 08acdaa96..fd7916147 100644 --- a/src/Fabulous/View.fs +++ b/src/Fabulous/View.fs @@ -8,6 +8,8 @@ module ViewHelpers = false else if (prevKey = Memo.MemoWidgetKey) then Memo.canReuseMemoizedWidget prevWidget currWidget + else if (prevKey = Component.WidgetKey) then + Component.canReuseComponent prevWidget currWidget else if (prevKey = MvuComponent.WidgetKey) then MvuComponent.canReuseMvuComponent prevWidget currWidget else @@ -15,7 +17,10 @@ module ViewHelpers = module View = /// Avoid recomputing the whole subtree when the key doesn't change - let lazy'<'msg, 'key, 'marker when 'key: equality> (fn: 'key -> WidgetBuilder<'msg, 'marker>) (key: 'key) : WidgetBuilder<'msg, Memo.Memoized<'marker>> = + let lazy'<'msg, 'key, 'marker when 'msg: equality and 'key: equality> + (fn: 'key -> WidgetBuilder<'msg, 'marker>) + (key: 'key) + : WidgetBuilder<'msg, Memo.Memoized<'marker>> = let memo: Memo.MemoData = { KeyData = box key