diff --git a/src/Fabulous/Binding.fs b/src/Fabulous/Binding.fs new file mode 100644 index 000000000..45e6190d2 --- /dev/null +++ b/src/Fabulous/Binding.fs @@ -0,0 +1,61 @@ +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 sourceKey = stateValue.Key + + let sub = + stateValue.Context.RenderNeeded.Subscribe(fun k -> + if k = sourceKey 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 @@ +