From feb42ddc526f21f6fb04737d0fdfb9ad8aa2e76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Fri, 22 Mar 2024 16:21:06 +0100 Subject: [PATCH 1/2] Add support for binding in components --- src/Fabulous/Binding.fs | 59 ++++++++++++++++++++++++++++++++ src/Fabulous/Component.fs | 2 +- src/Fabulous/ComponentContext.fs | 10 ++++-- src/Fabulous/Fabulous.fsproj | 1 + 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/Fabulous/Binding.fs diff --git a/src/Fabulous/Binding.fs b/src/Fabulous/Binding.fs new file mode 100644 index 000000000..8c5a69c4f --- /dev/null +++ b/src/Fabulous/Binding.fs @@ -0,0 +1,59 @@ +namespace Fabulous + +open System +open System.ComponentModel +open System.Runtime.CompilerServices + +type BindingRequest<'T> = delegate of unit -> StateValue<'T> + +[] +type BindingValue<'T> = + [] + val public SourceContext: ComponentContext + + [] + val public SourceKey: int + + new(stateValue: StateValue<'T>) = + { SourceContext = stateValue.Context + SourceKey = stateValue.Key } + + member this.Current = this.SourceContext.TryGetValue<'T>(this.SourceKey).Value + + member inline this.Set(value: 'T) = + this.SourceContext.SetValue(this.SourceKey, value) + +[] +module BindingBuilders = + type Context with + + static member inline Binding(value: StateValue<'T>) = BindingRequest<'T>(fun () -> value) + +[] +type BindingExtensions = + [] + static member inline Bind + ( + _: ComponentBuilder<'parentMsg>, + [] fn: BindingRequest<'T>, + [] continuation: BindingValue<'T> -> ComponentBodyBuilder<'marker> + ) = + ComponentBodyBuilder<'marker>(fun bindings ctx -> + let key = int bindings + let stateValue = fn.Invoke() + + // Dispose previous subscription + match ctx.TryGetValue(key) with + | ValueNone -> () + | ValueSome d -> d.Dispose() + + // Subscribe to source context changes + let sub = + stateValue.Context.RenderNeeded.Subscribe(fun k -> + if k = stateValue.Key then + ctx.NeedsRender(key)) + + ctx.SetValueInternal(key, sub) + + let bindingValue = BindingValue<'T>(stateValue) + (continuation bindingValue).Invoke(bindings, ctx)) diff --git a/src/Fabulous/Component.fs b/src/Fabulous/Component.fs index 92950f523..72a035910 100644 --- a/src/Fabulous/Component.fs +++ b/src/Fabulous/Component.fs @@ -332,7 +332,7 @@ type Component(treeContext: ViewTreeContext, body: ComponentBody, context: Compo interface IDisposable with member this.Dispose() = this.Dispose() - member this.Render() = + member this.Render(_) = treeContext.SyncAction(this.RenderInternal) module Component = diff --git a/src/Fabulous/ComponentContext.fs b/src/Fabulous/ComponentContext.fs index 14c2fd995..558b0fa7c 100644 --- a/src/Fabulous/ComponentContext.fs +++ b/src/Fabulous/ComponentContext.fs @@ -31,7 +31,7 @@ type ComponentContext(initialSize: int) = let mutable values = Array.zeroCreate initialSize let disposables = System.Collections.Generic.List() - let renderNeeded = Event() + let renderNeeded = Event() // We assume that most components will have few values, so initialize it with a small array new() = new ComponentContext(3) @@ -39,7 +39,7 @@ type ComponentContext(initialSize: int) = member this.Id = id member this.RenderNeeded = renderNeeded.Publish - member this.NeedsRender() = renderNeeded.Trigger() + member this.NeedsRender(key: int) = renderNeeded.Trigger(key) member private this.ResizeIfNeeded(count: int) = // If the array is already big enough, we don't need to do anything @@ -66,7 +66,7 @@ type ComponentContext(initialSize: int) = member this.SetValue(key: int, value: 'T) = this.SetValueInternal(key, value) - this.NeedsRender() + this.NeedsRender(key) member this.LinkDisposable(disposable: IDisposable) = disposables.Add(disposable) @@ -76,6 +76,10 @@ type ComponentContext(initialSize: int) = disposables.Clear() + for value in values do + if value :? IDisposable then + (value :?> IDisposable).Dispose() + values <- Array.empty interface IDisposable with diff --git a/src/Fabulous/Fabulous.fsproj b/src/Fabulous/Fabulous.fsproj index e5e292729..313be82b4 100644 --- a/src/Fabulous/Fabulous.fsproj +++ b/src/Fabulous/Fabulous.fsproj @@ -43,6 +43,7 @@ + From 177b6e4036d3ff43f7b44e51e2e22a7c4df7178b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Fri, 22 Mar 2024 16:32:54 +0100 Subject: [PATCH 2/2] Avoid capturing StateValue in closure --- src/Fabulous/Binding.fs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Fabulous/Binding.fs b/src/Fabulous/Binding.fs index 8c5a69c4f..45e6190d2 100644 --- a/src/Fabulous/Binding.fs +++ b/src/Fabulous/Binding.fs @@ -48,9 +48,11 @@ type BindingExtensions = | ValueSome d -> d.Dispose() // Subscribe to source context changes + let sourceKey = stateValue.Key + let sub = stateValue.Context.RenderNeeded.Subscribe(fun k -> - if k = stateValue.Key then + if k = sourceKey then ctx.NeedsRender(key)) ctx.SetValueInternal(key, sub)