Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split Attributes to Mvu & Components #1082

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Fabulous.Benchmarks/Fabulous.Benchmarks.fsproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Fabulous.Tests/APISketchTests/TestUI.Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ module TestUI_Attributes =

module Container =
let Children =
Attributes.defineListWidgetCollection "Container_Children" (fun target ->
ComponentAttributes.defineListWidgetCollection "Container_Children" (fun target ->
(target :?> IContainer).Children :> System.Collections.Generic.IList<_>)

let Tap = defineContainerTappable "Container_Tap"
Expand Down
2 changes: 1 addition & 1 deletion src/Fabulous.Tests/Fabulous.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
117 changes: 117 additions & 0 deletions src/Fabulous/Attributes.Components.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
namespace Fabulous

open System
open Fabulous
open Fabulous.ScalarAttributeDefinitions

module ComponentAttributes =
/// Define an attribute storing a collection of Widget for a List<T> property
let inline defineListWidgetCollection<'itemType> name ([<InlineIfLambda>] getCollection: obj -> System.Collections.Generic.IList<'itemType>) =
let applyDiff _ (diffs: WidgetCollectionItemChanges) (node: IViewNode) =
let targetColl = getCollection node.Target

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Remove(index, widget) ->
let itemNode = node.TreeContext.GetViewNode(box targetColl[index])

// Trigger the unmounted event
// TODO: Trigger Unmounted function for all children - Dispatcher.dispatchEventForAllChildren itemNode widget Lifecycle.Unmounted
itemNode.Dispose()

// Remove the child from the UI tree
targetColl.RemoveAt(index)

| _ -> ()

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Insert(index, widget) ->
let struct (itemNode, view) = Helpers.createViewForWidget node widget

// Insert the new child into the UI tree
targetColl.Insert(index, unbox view)

// Trigger the mounted event
// TODO: Trigger Mounted function for all children - Dispatcher.dispatchEventForAllChildren itemNode widget Lifecycle.Mounted

| WidgetCollectionItemChange.Update(index, widgetDiff) ->
let childNode = node.TreeContext.GetViewNode(box targetColl[index])

childNode.ApplyDiff(&widgetDiff)

| WidgetCollectionItemChange.Replace(index, oldWidget, newWidget) ->
let prevItemNode = node.TreeContext.GetViewNode(box targetColl[index])

let struct (nextItemNode, view) = Helpers.createViewForWidget node newWidget

// Trigger the unmounted event for the old child
// TODO: Trigger Unmounted function for all children - Dispatcher.dispatchEventForAllChildren prevItemNode oldWidget Lifecycle.Unmounted
prevItemNode.Dispose()

// Replace the existing child in the UI tree at the index with the new one
targetColl[index] <- unbox view

// Trigger the mounted event for the new child
// TODO: Trigger Mounted function for all children - Dispatcher.dispatchEventForAllChildren nextItemNode newWidget Lifecycle.Mounted

| _ -> ()

let updateNode _ (newValueOpt: ArraySlice<Widget> voption) (node: IViewNode) =
let targetColl = getCollection node.Target
targetColl.Clear()

match newValueOpt with
| ValueNone -> ()
| ValueSome widgets ->
for widget in ArraySlice.toSpan widgets do
let struct (_, view) = Helpers.createViewForWidget node widget

targetColl.Add(unbox view)

Attributes.defineWidgetCollection name applyDiff updateNode

/// Define an attribute for EventHandler
let inline defineEventNoArg name ([<InlineIfLambda>] getEvent: obj -> IEvent<EventHandler, EventArgs>) : SimpleScalarAttributeDefinition<unit -> unit> =
let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: (unit -> unit) voption) node ->
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.RemoveHandler(name)
| ValueSome(fn) ->
let event = getEvent node.Target
node.SetHandler(name, event.Subscribe(fun _ -> fn())))
)

|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }

/// Define an attribute for EventHandler<'T>
let inline defineEvent<'args>
name
([<InlineIfLambda>] getEvent: obj -> IEvent<EventHandler<'args>, 'args>)
: SimpleScalarAttributeDefinition<'args -> unit> =
let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: ('args -> unit) voption) node ->
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.RemoveHandler(name)
| ValueSome(fn) ->
let event = getEvent node.Target
node.SetHandler(name, event.Subscribe(fun args -> fn args)))
)

|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }
133 changes: 133 additions & 0 deletions src/Fabulous/Attributes.Mvu.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
namespace Fabulous

open System
open System.Runtime.CompilerServices
open Fabulous
open Fabulous.ScalarAttributeDefinitions

type MsgValue = MsgValue of obj

[<Extension>]
type SimpleScalarAttributeDefinitionExtensions() =
[<Extension>]
static member inline WithValue(this: SimpleScalarAttributeDefinition<'args -> MsgValue>, value: 'args -> 'msg) =
this.WithValue(value >> box >> MsgValue)

module MvuAttributes =

/// Define an attribute storing a collection of Widget for a List<T> property
let inline defineListWidgetCollection<'itemType> name ([<InlineIfLambda>] getCollection: obj -> System.Collections.Generic.IList<'itemType>) =
let applyDiff _ (diffs: WidgetCollectionItemChanges) (node: IViewNode) =
let targetColl = getCollection node.Target

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Remove(index, widget) ->
let itemNode = node.TreeContext.GetViewNode(box targetColl[index])

// Trigger the unmounted event
Dispatcher.dispatchEventForAllChildren itemNode widget MvuLifecycle.Unmounted
itemNode.Dispose()

// Remove the child from the UI tree
targetColl.RemoveAt(index)

| _ -> ()

for diff in diffs do
match diff with
| WidgetCollectionItemChange.Insert(index, widget) ->
let struct (itemNode, view) = Helpers.createViewForWidget node widget

// Insert the new child into the UI tree
targetColl.Insert(index, unbox view)

// Trigger the mounted event
Dispatcher.dispatchEventForAllChildren itemNode widget MvuLifecycle.Mounted

| WidgetCollectionItemChange.Update(index, widgetDiff) ->
let childNode = node.TreeContext.GetViewNode(box targetColl[index])

childNode.ApplyDiff(&widgetDiff)

| WidgetCollectionItemChange.Replace(index, oldWidget, newWidget) ->
let prevItemNode = node.TreeContext.GetViewNode(box targetColl[index])

let struct (nextItemNode, view) = Helpers.createViewForWidget node newWidget

// Trigger the unmounted event for the old child
Dispatcher.dispatchEventForAllChildren prevItemNode oldWidget MvuLifecycle.Unmounted
prevItemNode.Dispose()

// Replace the existing child in the UI tree at the index with the new one
targetColl[index] <- unbox view

// Trigger the mounted event for the new child
Dispatcher.dispatchEventForAllChildren nextItemNode newWidget MvuLifecycle.Mounted

| _ -> ()

let updateNode _ (newValueOpt: ArraySlice<Widget> voption) (node: IViewNode) =
let targetColl = getCollection node.Target
targetColl.Clear()

match newValueOpt with
| ValueNone -> ()
| ValueSome widgets ->
for widget in ArraySlice.toSpan widgets do
let struct (_, view) = Helpers.createViewForWidget node widget

targetColl.Add(unbox view)

Attributes.defineWidgetCollection name applyDiff updateNode

/// Define an attribute for EventHandler
let inline defineEventNoArg name ([<InlineIfLambda>] getEvent: obj -> IEvent<EventHandler, EventArgs>) : SimpleScalarAttributeDefinition<MsgValue> =
let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: MsgValue voption) node ->
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.RemoveHandler(name)
| ValueSome(MsgValue msg) ->
let event = getEvent node.Target
let handler = event.Subscribe(fun _ -> Dispatcher.dispatch node msg)
node.SetHandler(name, handler))
)

|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }

/// Define an attribute for EventHandler<'T>
let inline defineEvent<'args>
name
([<InlineIfLambda>] getEvent: obj -> IEvent<EventHandler<'args>, 'args>)
: SimpleScalarAttributeDefinition<'args -> MsgValue> =
let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: ('args -> MsgValue) voption) (node: IViewNode) ->
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.RemoveHandler(name)
| ValueSome fn ->
let event = getEvent node.Target

let handler =
event.Subscribe(fun args ->
let (MsgValue r) = fn args
Dispatcher.dispatch node r)

node.SetHandler(name, handler))
)
|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }
Loading
Loading