-
-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1051 from fabulous-dev/components
Add new Component API
- Loading branch information
Showing
8 changed files
with
652 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
namespace Fabulous | ||
|
||
open System.Runtime.CompilerServices | ||
|
||
(* | ||
The idea of Binding is to listen to a State<'T> that is managed by another Context and be able to update it | ||
while notifying the two Contexts involved (source and target) | ||
let child (count: BindingRequest<int>) = | ||
view { | ||
let! boundCount = bind count | ||
Button($"Count is {boundCount.Value}", fun () -> boundCount.Set(boundCount.Value + 1)) | ||
} | ||
let parent = | ||
view { | ||
let! count = state 0 | ||
VStack() { | ||
Text($"Count is {count.Value}") | ||
child (Binding.ofState count) | ||
} | ||
} | ||
*) | ||
|
||
type Binding<'T> = delegate of unit -> StateValue<'T> | ||
|
||
[<Struct>] | ||
type BindingValue<'T> = | ||
val public Context: ComponentContext | ||
val public SourceContext: ComponentContext | ||
val public SourceKey: int | ||
val public SourceCurrentValue: 'T | ||
|
||
new(ctx, sourceCtx, sourceKey, sourceCurrentValue) = | ||
{ Context = ctx | ||
SourceContext = sourceCtx | ||
SourceKey = sourceKey | ||
SourceCurrentValue = sourceCurrentValue } | ||
|
||
member inline this.Current = this.SourceCurrentValue | ||
|
||
member inline this.Set(value: 'T) = | ||
this.SourceContext.SetValue(this.SourceKey, value) | ||
this.Context.NeedsRender() | ||
|
||
[<Extension>] | ||
type BindingExtensions = | ||
[<Extension>] | ||
static member inline Bind | ||
( | ||
_: ComponentBuilder, | ||
[<InlineIfLambda>] request: Binding<'T>, | ||
[<InlineIfLambda>] continuation: BindingValue<'T> -> ComponentBodyBuilder<'msg, 'marker> | ||
) = | ||
// Despite its name, ComponentBinding actual value is not stored in this component, but in the source component | ||
// So, we do not need to increment the number of bindings here | ||
ComponentBodyBuilder(fun bindings ctx -> | ||
let source = request.Invoke() | ||
|
||
source.Context.RenderNeeded.Add(fun () -> ctx.NeedsRender()) | ||
|
||
let state = BindingValue<'T>(ctx, source.Context, source.Key, source.Current) | ||
(continuation state).Invoke(bindings, ctx)) | ||
|
||
[<AutoOpen>] | ||
module BindingHelpers = | ||
let inline ``$`` (source: StateValue<'T>) = Binding(fun () -> source) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
namespace Fabulous | ||
|
||
/// 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<'msg, 'marker> = | ||
delegate of bindings: int<binding> * context: ComponentContext -> struct (int<binding> * WidgetBuilder<'msg, 'marker>) | ||
|
||
type ComponentBuilder() = | ||
member inline this.Yield(widgetBuilder: WidgetBuilder<'msg, 'marker>) = | ||
ComponentBodyBuilder<'msg, 'marker>(fun bindings ctx -> struct (bindings, widgetBuilder)) | ||
|
||
member inline this.Combine([<InlineIfLambda>] a: ComponentBodyBuilder<'msg, 'marker>, [<InlineIfLambda>] b: ComponentBodyBuilder<'msg, 'marker>) = | ||
ComponentBodyBuilder<'msg, 'marker>(fun bindings ctx -> | ||
let struct (bindingsA, _) = a.Invoke(bindings, ctx) // discard the previous widget in the chain but we still need to count the bindings | ||
let struct (bindingsB, resultB) = b.Invoke(bindings, ctx) | ||
|
||
// Calculate the total number of bindings between A and B | ||
let resultBindings = (bindingsA + bindingsB) - bindings | ||
|
||
struct (resultBindings, resultB)) | ||
|
||
member inline this.Delay([<InlineIfLambda>] fn: unit -> ComponentBodyBuilder<'msg, 'marker>) = | ||
ComponentBodyBuilder<'msg, 'marker>(fun bindings ctx -> | ||
let sub = fn() | ||
sub.Invoke(bindings, ctx)) | ||
|
||
member inline this.Run([<InlineIfLambda>] body: ComponentBodyBuilder<'msg, 'marker>) = | ||
let compiledBody = | ||
ComponentBody(fun ctx -> | ||
let struct (_, result) = body.Invoke(0<binding>, ctx) | ||
struct (ctx, result.Compile())) | ||
|
||
WidgetBuilder<'msg, 'marker>(Component.WidgetKey, Component.Body.WithValue(compiledBody)) |
Oops, something went wrong.