Skip to content

Commit

Permalink
Merge pull request #68 from fabulous-dev/msg-equality
Browse files Browse the repository at this point in the history
Use Fabulous 3.0.0-pre6
  • Loading branch information
TimLariviere authored Oct 24, 2024
2 parents f73a88b + 02e5254 commit f119e9e
Show file tree
Hide file tree
Showing 107 changed files with 1,881 additions and 488 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

_No unreleased changes_

## [8.1.0-pre18] - 2024-09-24

### Fixed
- Use Fabulous 3.0.0-pre6

## [8.1.0-pre17] - 2024-03-26

### Fixed
Expand Down Expand Up @@ -240,7 +245,8 @@ Essentially v2.8.1 and v8.0.0 are similar except for the required .NET version.
### Changed
- Fabulous.MauiControls has moved from the Fabulous repository to its own repository: [https://github.com/fabulous-dev/Fabulous.MauiControls](https://github.com/fabulous-dev/Fabulous.MauiControls)

[unreleased]: https://github.com/fabulous-dev/Fabulous.MauiControls/compare/8.1.0-pre17...HEAD
[unreleased]: https://github.com/fabulous-dev/Fabulous.MauiControls/compare/8.1.0-pre18...HEAD
[8.1.0-pre18]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre18
[8.1.0-pre17]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre17
[8.1.0-pre16]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre16
[8.1.0-pre15]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre15
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Fabulous" Version="3.0.0-pre2" />
<PackageVersion Include="Fabulous" Version="3.0.0-pre6" />
<PackageVersion Include="FsCheck.NUnit" Version="2.16.6" />
<PackageVersion Include="FSharp.Core" Version="8.0.200" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
Expand Down
22 changes: 20 additions & 2 deletions src/Fabulous.MauiControls/AppHostBuilderExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type AppHostBuilderExtensions =
program.State.Logger,
program.SyncAction,
fun () ->
(View.Component(program.State, arg) {
(View.Component("_", program.State, arg) {
let! model = Mvu.State
program.View model
})
Expand All @@ -53,6 +53,24 @@ type AppHostBuilderExtensions =
static member UseFabulousApp(this: MauiAppBuilder, program: Program<unit, 'model, 'msg, #IFabApplication>) : MauiAppBuilder =
this.UseFabulousApp(program, ())

[<Extension>]
static member UseFabulousApp(this: MauiAppBuilder, program: Program<'arg, 'model, 'msg, Memo.Memoized<#IFabApplication>>, arg: 'arg) : MauiAppBuilder =
this.UseFabulousApp(
program.CanReuseView,
program.State.Logger,
program.SyncAction,
fun () ->
(View.Component("_", program.State, arg) {
let! model = Mvu.State
program.View model
})
.Compile()
)

[<Extension>]
static member UseFabulousApp(this: MauiAppBuilder, program: Program<unit, 'model, 'msg, Memo.Memoized<#IFabApplication>>) : MauiAppBuilder =
this.UseFabulousApp(program, ())

[<Extension>]
static member UseFabulousApp
(
Expand All @@ -72,5 +90,5 @@ type AppHostBuilderExtensions =
(match syncAction with
| Some synAction -> synAction
| None -> MauiViewHelpers.defaultSyncAction),
fun () -> (View.Component() { view() }).Compile()
fun () -> (View.Component("_") { view() }).Compile()
)
63 changes: 59 additions & 4 deletions src/Fabulous.MauiControls/Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@ type ImageSourceValue =
| Stream of stream: Stream

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

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

[<Struct>]
type ValueEventData<'data, 'eventArgs> =
{ Value: 'data
Event: 'eventArgs -> unit }

module ValueEventData =
let create (value: 'data) (event: 'eventArgs -> unit) = { Value = value; Event = event }

/// Maui.Controls specific attributes that can be encoded as 8 bytes
module SmallScalars =
module LayoutOptions =
Expand Down Expand Up @@ -157,12 +165,12 @@ module Attributes =
name
(bindableProperty: BindableProperty)
(getEvent: obj -> IEvent<EventHandler<'args>, 'args>)
: SimpleScalarAttributeDefinition<ValueEventData<'data, 'args>> =
: SimpleScalarAttributeDefinition<MsgValueEventData<'data, 'args>> =

let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun oldValueOpt (newValueOpt: ValueEventData<'data, 'args> voption) node ->
(fun oldValueOpt (newValueOpt: MsgValueEventData<'data, 'args> voption) node ->
let target = node.Target :?> BindableObject

match newValueOpt with
Expand Down Expand Up @@ -199,3 +207,50 @@ module Attributes =
|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }

/// Update both a property and its related event.
/// This definition makes sure that the event is only raised when the property is changed by the user,
/// and not when the property is set by the code
let defineBindableWithEventNoDispatch<'data, 'args>
name
(bindableProperty: BindableProperty)
(getEvent: obj -> IEvent<EventHandler<'args>, 'args>)
: SimpleScalarAttributeDefinition<ValueEventData<'data, 'args>> =

let key =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun oldValueOpt (newValueOpt: ValueEventData<'data, 'args> voption) node ->
let target = node.Target :?> BindableObject

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

// Only clear the property if a value was set before
match oldValueOpt with
| ValueNone -> ()
| ValueSome _ -> target.ClearValue(bindableProperty)

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

// Set the new value
target.SetValue(bindableProperty, curr.Value)

// Set the new event handler
let event = getEvent target

let handler = event.Subscribe(curr.Event)

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

{ Key = key; Name = name }
23 changes: 16 additions & 7 deletions src/Fabulous.MauiControls/Component.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ module Component =
module ComponentBuilders =
type Fabulous.Maui.View with

static member inline Component<'msg, 'marker>() = ComponentBuilder<'msg>()

static member inline Component<'msg, 'model, 'marker, 'parentMsg>(program: Program<unit, 'model, 'msg>) =
MvuComponentBuilder<unit, 'msg, 'model, 'marker, 'parentMsg>(program, ())

static member inline Component<'arg, 'msg, 'model, 'marker, 'parentMsg>(program: Program<'arg, 'model, 'msg>, arg: 'arg) =
MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg>(program, arg)
static member inline Component<'msg, 'marker when 'msg: equality>(key: string) = ComponentBuilder<'msg>(key)

static member inline Component<'msg, 'model, 'marker, 'parentMsg when 'msg: equality and 'parentMsg: equality>
(
key: string,
program: Program<unit, 'model, 'msg>
) =
MvuComponentBuilder<unit, 'msg, 'model, 'marker, 'parentMsg>(key, program, ())

static member inline Component<'arg, 'msg, 'model, 'marker, 'parentMsg when 'msg: equality and 'parentMsg: equality>
(
key: string,
program: Program<'arg, 'model, 'msg>,
arg: 'arg
) =
MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg>(key, program, arg)
3 changes: 2 additions & 1 deletion src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<Compile Include="Views\Layouts\_StackBase.fs" />
<Compile Include="Views\Layouts\VerticalStackLayout.fs" />
<Compile Include="Views\Layouts\HorizontalStackLayout.fs" />
<Compile Include="Views\Layouts\ZStack.fs" />
<Compile Include="Views\Layouts\Grid.fs" />
<Compile Include="Views\Layouts\ScrollView.fs" />
<Compile Include="Views\Layouts\Border.fs" />
Expand Down Expand Up @@ -156,7 +157,7 @@
This version will be used as the lower bound in the NuGet package
-->
<PackageReference Include="FSharp.Core" VersionOverride="8.0.100" PrivateAssets="All" />
<PackageReference Include="Fabulous" Condition="'$(UseLocalProjectReference)' != 'true'" VersionOverride="[3.0.0-pre2]" />
<PackageReference Include="Fabulous" Condition="'$(UseLocalProjectReference)' != 'true'" VersionOverride="[3.0.0-pre6]" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<PackageReference Include="Microsoft.Maui.Controls" VersionOverride="8.0.3" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" VersionOverride="8.0.3" />
Expand Down
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Style.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type FabElementExtensions =
/// <param name="this">Current widget</param>
/// <param name="fn">The style modifier function</param>
[<Extension>]
static member inline style<'msg, 'marker when 'marker :> IFabElement>
static member inline style<'msg, 'marker when 'msg: equality and 'marker :> IFabElement>
(
this: WidgetBuilder<'msg, 'marker>,
fn: WidgetBuilder<'msg, 'marker> -> WidgetBuilder<'msg, 'marker>
Expand Down
10 changes: 10 additions & 0 deletions src/Fabulous.MauiControls/Views/Any.fs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ module AnyBuilders =
/// <param name="this">Current widget</param>
static member AnyCell(this: WidgetBuilder<'msg, Memo.Memoized<#IFabCell>>) =
WidgetBuilder<'msg, IFabCell>(this.Key, this.Attributes)

/// <summary>Downcast to Shape widget to allow to return different types of shapes in a single expression (e.g. if/else, match with pattern, etc.)</summary>
/// <param name="this">Current widget</param>
static member AnyShape(this: WidgetBuilder<'msg, #IFabShape>) =
WidgetBuilder<'msg, IFabShape>(this.Key, this.Attributes)

/// <summary>Downcast to Shape widget to allow to return different types of shapes in a single expression (e.g. if/else, match with pattern, etc.)</summary>
/// <param name="this">Current widget</param>
static member AnyShape(this: WidgetBuilder<'msg, Memo.Memoized<#IFabShape>>) =
WidgetBuilder<'msg, IFabShape>(this.Key, this.Attributes)
Loading

0 comments on commit f119e9e

Please sign in to comment.