Skip to content

Commit

Permalink
Split mvu and component
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarfgp committed Jul 21, 2024
1 parent 4495296 commit 3dabe55
Show file tree
Hide file tree
Showing 32 changed files with 632 additions and 370 deletions.
6 changes: 6 additions & 0 deletions Fabulous.Avalonia.sln
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Playground", "samples\Playg
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous.Avalonia.Diagnostics", "extensions\Fabulous.Avalonia.Diagnostics\Fabulous.Avalonia.Diagnostics.fsproj", "{C4B5FB7F-D3A9-46A7-9EDF-317914F7172A}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous", "..\Fabulous\src\Fabulous\Fabulous.fsproj", "{806C5101-ED21-4497-8B4B-84FBF64D1C68}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -135,6 +137,10 @@ Global
{C4B5FB7F-D3A9-46A7-9EDF-317914F7172A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4B5FB7F-D3A9-46A7-9EDF-317914F7172A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4B5FB7F-D3A9-46A7-9EDF-317914F7172A}.Release|Any CPU.Build.0 = Release|Any CPU
{806C5101-ED21-4497-8B4B-84FBF64D1C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{806C5101-ED21-4497-8B4B-84FBF64D1C68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{806C5101-ED21-4497-8B4B-84FBF64D1C68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{806C5101-ED21-4497-8B4B-84FBF64D1C68}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9EB9291D-09F8-43DD-9A95-154D4E6BA78E} = {60AC1554-2FB2-4AED-90F5-A4F99F7DDF5E}
Expand Down
2 changes: 1 addition & 1 deletion samples/Gallery/Gallery.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
<ItemGroup>
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="FSharp.Core" />
<PackageReference Include="Fabulous" />
<!-- <PackageReference Include="Fabulous" />-->
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Fabulous.Avalonia/Application.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ open Fabulous.StackAllocatedCollections.StackList
#nowarn "0044" // Disable obsolete warnings in Fabulous.Avalonia. Please remove after deleting obsolete code.

type IFabApplication =
inherit IFabAvaloniaObject
inherit IFabElement

type FabApplication() =
inherit Application()
Expand Down
261 changes: 261 additions & 0 deletions src/Fabulous.Avalonia/Attributes.Components.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
namespace Fabulous.Avalonia

open System.Collections
open System.ComponentModel
open Avalonia
open Avalonia.Collections
open Avalonia.Interactivity
open Fabulous
open Fabulous.ScalarAttributeDefinitions

[<Struct>]
type ComponentValueEventData<'data, 'eventArgs> =
{ Value: 'data voption
Event: 'eventArgs -> MsgValue }

module ComponentValueEventData =
let create (value: 'data) (event: 'eventArgs -> 'msg) =
{ Value = ValueSome value
Event = event >> box >> MsgValue }

let createVOption (value: 'data voption) (event: 'eventArgs -> 'msg) =
{ Value = value
Event = event >> box >> MsgValue }

module ComponentAttributes =
let inline defineAvaloniaPropertyWithChangedEvent<'modelType, 'valueType>
name
(property: AvaloniaProperty<'valueType>)
([<InlineIfLambda>] convertToValue: 'modelType -> 'valueType)
([<InlineIfLambda>] convertToModel: 'valueType -> 'modelType)
: SimpleScalarAttributeDefinition<ComponentValueEventData<'modelType, 'modelType>> =

let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun oldValueOpt (newValueOpt: ComponentValueEventData<'modelType, 'modelType> voption) node ->
let target = node.Target :?> AvaloniaObject

// The attribute is no longer applied, so we clean up the event
match node.TryGetHandler(property.Name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone ->
match oldValueOpt with
| ValueNone -> ()
| ValueSome _ -> target.ClearValue(property)

| ValueSome curr ->
// Clean up the old event handler if any
match node.TryGetHandler(property.Name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

// Set the new value

match curr.Value with
| ValueNone -> ()
| ValueSome v ->
let newValue = convertToValue v
target.SetValue(property, box newValue) |> ignore

// Set the new event handler
let disposable = property.Changed.Subscribe(fun args -> curr.Event |> ignore)

node.SetHandler(property.Name, disposable))
)
|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }

let defineAvaloniaPropertyWithChangedEvent'<'T> name (property: AvaloniaProperty<'T>) : SimpleScalarAttributeDefinition<ComponentValueEventData<'T, 'T>> =
defineAvaloniaPropertyWithChangedEvent<'T, 'T> name property id id

let defineRoutedEvent<'args when 'args :> RoutedEventArgs> name (property: RoutedEvent<'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.Dispose()

| ValueSome fn ->
let event = property.AddClassHandler(fun _ args -> fn args |> ignore)

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

{ Key = key; Name = name }

let inline defineCancelEvent
name
([<InlineIfLambda>] getEvent: obj -> IEvent<CancelEventHandler, CancelEventArgs>)
: SimpleScalarAttributeDefinition<CancelEventArgs -> MsgValue> =
let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: (CancelEventArgs -> MsgValue) voption) (node: IViewNode) ->
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.Dispose()

| ValueSome fn ->
let event = getEvent node.Target

let handler = event.Subscribe(fun args -> fn args |> ignore)

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

{ Key = key; Name = name }

/// Define an attribute storing a collection of Widget for a List<T> property
let inline defineAvaloniaNonGenericListWidgetCollection name ([<InlineIfLambda>] getCollection: obj -> IList) =
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(targetColl[index])

// FIXME ? Trigger the unmounted event
// // Trigger the unmounted event
// Dispatcher.dispatchEventForAllChildren itemNode widget ComponentLifecycle.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)

// FIXME ? Trigger the unmounted event
// Trigger the mounted event
// Dispatcher.dispatchEventForAllChildren itemNode widget ComponentLifecycle.Mounted

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

childNode.ApplyDiff(&widgetDiff)

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

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

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

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

// Trigger the mounted event for the new child
// FIXME ? Trigger the unmounted event
// Dispatcher.dispatchEventForAllChildren nextItemNode newWidget ComponentLifecycle.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(view) |> ignore

Attributes.defineWidgetCollection name applyDiff updateNode

/// Define an attribute storing a collection of Widget for a AvaloniaList<T> property
let defineAvaloniaListWidgetCollection<'itemType> name (getCollection: obj -> IAvaloniaList<'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
// FIXME ? Trigger the unmounted event
// Dispatcher.dispatchEventForAllChildren itemNode widget ComponentLifecycle.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
// FIXME ? Trigger the unmounted event
// Dispatcher.dispatchEventForAllChildren itemNode widget ComponentLifecycle.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

// FIXME ? Trigger the unmounted event
// Trigger the unmounted event for the old child
// Dispatcher.dispatchEventForAllChildren prevItemNode oldWidget ComponentLifecycle.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
// FIXME ? Trigger the unmounted event
// Dispatcher.dispatchEventForAllChildren nextItemNode newWidget ComponentLifecycle.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
Loading

0 comments on commit 3dabe55

Please sign in to comment.