Skip to content

Commit

Permalink
Prevent confusion between msg and fn by using equality constraint
Browse files Browse the repository at this point in the history
TimLariviere committed Aug 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent f73a88b commit 8dd64e4
Showing 102 changed files with 1,760 additions and 481 deletions.
18 changes: 18 additions & 0 deletions src/Fabulous.MauiControls/AppHostBuilderExtensions.fs
Original file line number Diff line number Diff line change
@@ -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
(
66 changes: 62 additions & 4 deletions src/Fabulous.MauiControls/Attributes.fs
Original file line number Diff line number Diff line change
@@ -16,14 +16,24 @@ 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 =
@@ -157,12 +167,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
@@ -199,3 +209,51 @@ 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 }
6 changes: 3 additions & 3 deletions src/Fabulous.MauiControls/Component.fs
Original file line number Diff line number Diff line change
@@ -17,10 +17,10 @@ module Component =
module ComponentBuilders =
type Fabulous.Maui.View with

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

static member inline Component<'msg, 'model, 'marker, 'parentMsg>(program: Program<unit, 'model, 'msg>) =
static member inline Component<'msg, 'model, 'marker, 'parentMsg when 'msg : equality and 'parentMsg : equality>(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) =
static member inline Component<'arg, 'msg, 'model, 'marker, 'parentMsg when 'msg : equality and 'parentMsg : equality>(program: Program<'arg, 'model, 'msg>, arg: 'arg) =
MvuComponentBuilder<'arg, 'msg, 'model, 'marker, 'parentMsg>(program, arg)
3 changes: 2 additions & 1 deletion src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseLocalProjectReference>false</UseLocalProjectReference>
<UseLocalProjectReference>true</UseLocalProjectReference>

<TargetFramework>net8.0</TargetFramework>
<UseMaui>true</UseMaui>
@@ -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" />
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Style.fs
Original file line number Diff line number Diff line change
@@ -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>
148 changes: 119 additions & 29 deletions src/Fabulous.MauiControls/Views/Application.fs
Original file line number Diff line number Diff line change
@@ -67,32 +67,59 @@ module Application =
Attributes.definePropertyWidget "Application_MainPage" (fun target -> (target :?> Application).MainPage :> obj) (fun target value ->
(target :?> Application).MainPage <- value)

let ModalPopped =
Attributes.defineEvent<ModalPoppedEventArgs> "Application_ModalPopped" (fun target -> (target :?> Application).ModalPopped)
let ModalPoppedMsg =
Attributes.defineEvent<ModalPoppedEventArgs> "Application_ModalPoppedMsg" (fun target -> (target :?> Application).ModalPopped)

let ModalPopping =
Attributes.defineEvent<ModalPoppingEventArgs> "Application_ModalPopping" (fun target -> (target :?> Application).ModalPopping)
let ModalPoppedFn =
Attributes.defineEventNoDispatch<ModalPoppedEventArgs> "Application_ModalPoppedFn" (fun target -> (target :?> Application).ModalPopped)

let ModalPushed =
Attributes.defineEvent<ModalPushedEventArgs> "Application_ModalPushed" (fun target -> (target :?> Application).ModalPushed)
let ModalPoppingMsg =
Attributes.defineEvent<ModalPoppingEventArgs> "Application_ModalPoppingMsg" (fun target -> (target :?> Application).ModalPopping)

let ModalPushing =
Attributes.defineEvent<ModalPushingEventArgs> "Application_ModalPushing" (fun target -> (target :?> Application).ModalPushing)
let ModalPoppingFn =
Attributes.defineEventNoDispatch<ModalPoppingEventArgs> "Application_ModalPoppingFn" (fun target -> (target :?> Application).ModalPopping)

let RequestedThemeChanged =
Attributes.defineEvent<AppThemeChangedEventArgs> "Application_RequestedThemeChanged" (fun target -> (target :?> Application).RequestedThemeChanged)
let ModalPushedMsg =
Attributes.defineEvent<ModalPushedEventArgs> "Application_ModalPushedMsg" (fun target -> (target :?> Application).ModalPushed)

let Resume =
Attributes.defineEventNoArg "Application_Resume" (fun target -> (target :?> FabApplication).Resume)
let ModalPushedFn =
Attributes.defineEventNoDispatch<ModalPushedEventArgs> "Application_ModalPushedFn" (fun target -> (target :?> Application).ModalPushed)

let Sleep =
Attributes.defineEventNoArg "Application_Sleep" (fun target -> (target :?> FabApplication).Sleep)
let ModalPushingMsg =
Attributes.defineEvent<ModalPushingEventArgs> "Application_ModalPushingMsg" (fun target -> (target :?> Application).ModalPushing)

let Start =
Attributes.defineEventNoArg "Application_Start" (fun target -> (target :?> FabApplication).Start)
let ModalPushingFn =
Attributes.defineEventNoDispatch<ModalPushingEventArgs> "Application_ModalPushingFn" (fun target -> (target :?> Application).ModalPushing)

let AppLinkRequestReceived =
Attributes.defineEvent "Application_AppLinkRequestReceived" (fun target -> (target :?> FabApplication).AppLinkRequestReceived)
let RequestedThemeChangedMsg =
Attributes.defineEvent<AppThemeChangedEventArgs> "Application_RequestedThemeChangedMsg" (fun target -> (target :?> Application).RequestedThemeChanged)

let RequestedThemeChangedFn =
Attributes.defineEventNoDispatch<AppThemeChangedEventArgs> "Application_RequestedThemeChangedFn" (fun target -> (target :?> Application).RequestedThemeChanged)

let ResumeMsg =
Attributes.defineEventNoArg "Application_ResumeMsg" (fun target -> (target :?> FabApplication).Resume)

let ResumeFn =
Attributes.defineEventNoArgNoDispatch "Application_ResumeFn" (fun target -> (target :?> FabApplication).Resume)

let SleepMsg =
Attributes.defineEventNoArg "Application_SleepMsg" (fun target -> (target :?> FabApplication).Sleep)

let SleepFn =
Attributes.defineEventNoArgNoDispatch "Application_SleepFn" (fun target -> (target :?> FabApplication).Sleep)

let StartMsg =
Attributes.defineEventNoArg "Application_StartMsg" (fun target -> (target :?> FabApplication).Start)

let StartFn =
Attributes.defineEventNoArgNoDispatch "Application_StartFn" (fun target -> (target :?> FabApplication).Start)

let AppLinkRequestReceivedMsg =
Attributes.defineEvent "Application_AppLinkRequestReceivedMsg" (fun target -> (target :?> FabApplication).AppLinkRequestReceived)

let AppLinkRequestReceivedFn =
Attributes.defineEventNoDispatch "Application_AppLinkRequestReceivedFn" (fun target -> (target :?> FabApplication).AppLinkRequestReceived)

let UserAppTheme =
Attributes.defineEnum<AppTheme> "Application_UserAppTheme" (fun _ newValueOpt node ->
@@ -118,7 +145,7 @@ module ApplicationBuilders =
WidgetHelpers.buildWidgets<'msg, IFabApplication> Application.WidgetKey [| Application.MainPage.WithValue(mainPage.Compile()) |]

/// <summary>Create an Application widget with a list of windows</summary>
static member inline Application<'msg, 'itemMarker when 'itemMarker :> IFabWindow>() =
static member inline Application<'msg, 'itemMarker when 'msg : equality and 'itemMarker :> IFabWindow>() =
CollectionBuilder<'msg, IFabApplication, 'itemMarker>(Application.WidgetKey, Application.Windows)

[<Extension>]
@@ -128,63 +155,126 @@ type ApplicationModifiers =
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPopped(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPoppedEventArgs -> 'msg) =
this.AddScalar(Application.ModalPopped.WithValue(fn))
this.AddScalar(Application.ModalPoppedMsg.WithValue(fn))

/// <summary>Listen for the ModalPopped event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPopped(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPoppedEventArgs -> unit) =
this.AddScalar(Application.ModalPoppedFn.WithValue(fn))

/// <summary>Listen for the ModalPopping event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPopping(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPoppingEventArgs -> 'msg) =
this.AddScalar(Application.ModalPopping.WithValue(fn))
this.AddScalar(Application.ModalPoppingMsg.WithValue(fn))

/// <summary>Listen for the ModalPopping event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPopping(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPoppingEventArgs -> unit) =
this.AddScalar(Application.ModalPoppingFn.WithValue(fn))

/// <summary>Listen for the ModalPushed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPushed(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPushedEventArgs -> 'msg) =
this.AddScalar(Application.ModalPushed.WithValue(fn))
this.AddScalar(Application.ModalPushedMsg.WithValue(fn))

/// <summary>Listen for the ModalPushed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPushed(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPushedEventArgs -> unit) =
this.AddScalar(Application.ModalPushedFn.WithValue(fn))

/// <summary>Listen for the ModalPushing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPushing(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPushingEventArgs -> 'msg) =
this.AddScalar(Application.ModalPushing.WithValue(fn))
this.AddScalar(Application.ModalPushingMsg.WithValue(fn))

/// <summary>Listen for the ModalPushing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onModalPushing(this: WidgetBuilder<'msg, #IFabApplication>, fn: ModalPushingEventArgs -> unit) =
this.AddScalar(Application.ModalPushingFn.WithValue(fn))

/// <summary>Listen for the Resume event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onResume(this: WidgetBuilder<'msg, #IFabApplication>, msg: 'msg) =
this.AddScalar(Application.Resume.WithValue(MsgValue(msg)))
this.AddScalar(Application.ResumeMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Resume event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onResume(this: WidgetBuilder<'msg, #IFabApplication>, fn: unit -> unit) =
this.AddScalar(Application.ResumeFn.WithValue(fn))

/// <summary>Listen for the Start event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onStart(this: WidgetBuilder<'msg, #IFabApplication>, msg: 'msg) =
this.AddScalar(Application.Start.WithValue(MsgValue(msg)))
this.AddScalar(Application.StartMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Start event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onStart(this: WidgetBuilder<'msg, #IFabApplication>, fn: unit -> unit) =
this.AddScalar(Application.StartFn.WithValue(fn))

/// <summary>Listen for the Sleep event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onSleep(this: WidgetBuilder<'msg, #IFabApplication>, msg: 'msg) =
this.AddScalar(Application.Sleep.WithValue(MsgValue(msg)))
this.AddScalar(Application.SleepMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Sleep event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onSleep(this: WidgetBuilder<'msg, #IFabApplication>, fn: unit -> unit) =
this.AddScalar(Application.SleepFn.WithValue(fn))

/// <summary>Listen for the RequestedThemeChanged event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onRequestedThemeChanged(this: WidgetBuilder<'msg, #IFabApplication>, fn: AppTheme -> 'msg) =
this.AddScalar(Application.RequestedThemeChanged.WithValue(fun args -> fn args.RequestedTheme |> box))
this.AddScalar(Application.RequestedThemeChangedMsg.WithValue(fun args -> fn args.RequestedTheme |> box))

/// <summary>Listen for the RequestedThemeChanged event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onRequestedThemeChanged(this: WidgetBuilder<'msg, #IFabApplication>, fn: AppTheme -> unit) =
this.AddScalar(Application.RequestedThemeChangedFn.WithValue(fun args -> fn args.RequestedTheme))

/// <summary>Listen for the AppLinkRequestReceived event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onAppLinkRequestReceived(this: WidgetBuilder<'msg, #IFabApplication>, fn: Uri -> 'msg) =
this.AddScalar(Application.AppLinkRequestReceived.WithValue(fun args -> fn args |> box))
this.AddScalar(Application.AppLinkRequestReceivedMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the AppLinkRequestReceived event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onAppLinkRequestReceived(this: WidgetBuilder<'msg, #IFabApplication>, fn: Uri -> unit) =
this.AddScalar(Application.AppLinkRequestReceivedFn.WithValue(fun args -> fn args))

/// <summary>Set the user app theme</summary>
/// <param name="this">Current widget</param>
@@ -203,7 +293,7 @@ type ApplicationModifiers =
[<Extension>]
type ApplicationYieldExtensions =
[<Extension>]
static member inline Yield<'msg, 'marker, 'itemType when 'marker :> IFabApplication and 'itemType :> IFabWindow>
static member inline Yield<'msg, 'marker, 'itemType when 'msg : equality and 'marker :> IFabApplication and 'itemType :> IFabWindow>
(
_: CollectionBuilder<'msg, 'marker, IFabWindow>,
x: WidgetBuilder<'msg, 'itemType>
Original file line number Diff line number Diff line change
@@ -21,13 +21,13 @@ module LinearGradientBrushBuilders =
type Fabulous.Maui.View with

/// <summary>Create a LinearGradientBrush widgets that paints an area with a linear gradient, which blends two or more colors along a line known as the gradient axis</summary>
static member inline LinearGradientBrush<'msg>() =
static member inline LinearGradientBrush() =
CollectionBuilder<'msg, IFabLinearGradientBrush, IFabGradientStop>(LinearGradientBrush.WidgetKey, GradientBrush.Children)

/// <summary>Create a LinearGradientBrush widgets that paints an area with a linear gradient, which blends two or more colors along a line known as the gradient axis</summary>
/// <param name="endPoint">EndPoint, of type Point, which represents the ending two-dimensional coordinates of the linear gradient</param>
/// <param name="startPoint">StartPoint, of type Point, which represents the starting two-dimensional coordinates of the linear gradient</param>
static member inline LinearGradientBrush<'msg>(startPoint: Point, endPoint: Point) =
static member inline LinearGradientBrush(startPoint: Point, endPoint: Point) =
CollectionBuilder<'msg, IFabLinearGradientBrush, IFabGradientStop>(
LinearGradientBrush.WidgetKey,
GradientBrush.Children,
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ module RadialGradientBrushBuilders =
/// <summary>Create a RadialGradientBrush widget that paints an area with a radial gradient, which blends two or more colors across a circle</summary>
/// <param name="center">Center, of type Point, which represents the center point of the circle for the radial gradient</param>
/// <param name="radius">Radius, of type float, which represents the radius of the circle for the radial gradient</param>
static member inline RadialGradientBrush<'msg>(center: Point, radius: float) =
static member inline RadialGradientBrush(center: Point, radius: float) =
CollectionBuilder<'msg, IFabRadialGradientBrush, IFabGradientStop>(
RadialGradientBrush.WidgetKey,
GradientBrush.Children,
@@ -31,5 +31,5 @@ module RadialGradientBrushBuilders =
)

/// <summary>Create a RadialGradientBrush widget that paints an area with a radial gradient, which blends two or more colors across a circle</summary>
static member inline RadialGradientBrush<'msg>() =
static member inline RadialGradientBrush() =
CollectionBuilder<'msg, IFabRadialGradientBrush, IFabGradientStop>(RadialGradientBrush.WidgetKey, GradientBrush.Children)
38 changes: 31 additions & 7 deletions src/Fabulous.MauiControls/Views/Cells/EntryCell.fs
Original file line number Diff line number Diff line change
@@ -46,14 +46,20 @@ module EntryCell =

let LabelColor = Attributes.defineBindableColor EntryCell.LabelColorProperty

let OnCompleted =
Attributes.defineEventNoArg "EntryCell_Completed" (fun target -> (target :?> EntryCell).Completed)
let OnCompletedMsg =
Attributes.defineEventNoArg "EntryCell_CompletedMsg" (fun target -> (target :?> EntryCell).Completed)

let OnCompletedFn =
Attributes.defineEventNoArgNoDispatch "EntryCell_CompletedFn" (fun target -> (target :?> EntryCell).Completed)

let Placeholder =
Attributes.defineBindableWithEquality<string> EntryCell.PlaceholderProperty

let TextWithEvent =
Attributes.defineBindableWithEvent "EntryCell_TextChanged" EntryCell.TextProperty (fun target -> (target :?> FabEntryCell).TextChanged)
let TextWithEventMsg =
Attributes.defineBindableWithEvent "EntryCell_TextChangedMsg" EntryCell.TextProperty (fun target -> (target :?> FabEntryCell).TextChanged)

let TextWithEventFn =
Attributes.defineBindableWithEventNoDispatch "EntryCell_TextChangedFn" EntryCell.TextProperty (fun target -> (target :?> FabEntryCell).TextChanged)

let VerticalTextAlignment =
Attributes.defineBindableEnum<TextAlignment> EntryCell.VerticalTextAlignmentProperty
@@ -66,11 +72,22 @@ module EntryCellBuilders =
/// <param name="label">The label value</param>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline EntryCell<'msg>(label: string, text: string, onTextChanged: string -> 'msg) =
static member inline EntryCell(label: string, text: string, onTextChanged: string -> 'msg) =
WidgetBuilder<'msg, IFabEntryCell>(
EntryCell.WidgetKey,
EntryCell.Label.WithValue(label),
EntryCell.TextWithEvent.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
EntryCell.TextWithEventMsg.WithValue(MsgValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

/// <summary>Create an EntryCell with a label, a text, and listen to text changes</summary>
/// <param name="label">The label value</param>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline EntryCell(label: string, text: string, onTextChanged: string -> unit) =
WidgetBuilder<'msg, IFabEntryCell>(
EntryCell.WidgetKey,
EntryCell.Label.WithValue(label),
EntryCell.TextWithEventFn.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

[<Extension>]
@@ -101,7 +118,14 @@ type EntryCellModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEntryCell>, msg: 'msg) =
this.AddScalar(EntryCell.OnCompleted.WithValue(MsgValue(msg)))
this.AddScalar(EntryCell.OnCompletedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen to the Completed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEntryCell>, fn: unit -> unit) =
this.AddScalar(EntryCell.OnCompletedFn.WithValue(fn))

/// <summary>Set the placeholder text</summary>
/// <param name="this">Current widget</param>
8 changes: 4 additions & 4 deletions src/Fabulous.MauiControls/Views/Cells/ImageCell.fs
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ module ImageCellBuilders =
/// <summary>Create an ImageCell widget with a text and an image source</summary>
/// <param name="text">The text of the cell</param>
/// <param name="source">The image source</param>
static member inline ImageCell<'msg>(text: string, source: ImageSource) =
static member inline ImageCell(text: string, source: ImageSource) =
WidgetBuilder<'msg, IFabImageCell>(
ImageCell.WidgetKey,
TextCell.Text.WithValue(text),
@@ -31,7 +31,7 @@ module ImageCellBuilders =
/// <summary>Create an ImageCell widget with a text and an image source</summary>
/// <param name="text">The text of the cell</param>
/// <param name="source">The image source</param>
static member inline ImageCell<'msg>(text: string, source: string) =
static member inline ImageCell(text: string, source: string) =
WidgetBuilder<'msg, IFabImageCell>(
ImageCell.WidgetKey,
TextCell.Text.WithValue(text),
@@ -41,13 +41,13 @@ module ImageCellBuilders =
/// <summary>Create an ImageCell widget with a text and an image source</summary>
/// <param name="text">The text of the cell</param>
/// <param name="source">The image source</param>
static member inline ImageCell<'msg>(text: string, source: Uri) =
static member inline ImageCell(text: string, source: Uri) =
WidgetBuilder<'msg, IFabImageCell>(ImageCell.WidgetKey, TextCell.Text.WithValue(text), ImageCell.ImageSource.WithValue(ImageSourceValue.Uri source))

/// <summary>Create an ImageCell widget with a text and an image source</summary>
/// <param name="text">The text of the cell</param>
/// <param name="source">The image source</param>
static member inline ImageCell<'msg>(text: string, source: Stream) =
static member inline ImageCell(text: string, source: Stream) =
WidgetBuilder<'msg, IFabImageCell>(
ImageCell.WidgetKey,
TextCell.Text.WithValue(text),
22 changes: 18 additions & 4 deletions src/Fabulous.MauiControls/Views/Cells/SwitchCell.fs
Original file line number Diff line number Diff line change
@@ -13,8 +13,11 @@ module SwitchCell =

let OnColor = Attributes.defineBindableColor SwitchCell.OnColorProperty

let OnWithEvent =
Attributes.defineBindableWithEvent "SwitchCell_OnChanged" SwitchCell.OnProperty (fun target -> (target :?> SwitchCell).OnChanged)
let OnWithEventMsg =
Attributes.defineBindableWithEvent "SwitchCell_OnChangedMsg" SwitchCell.OnProperty (fun target -> (target :?> SwitchCell).OnChanged)

let OnWithEventFn =
Attributes.defineBindableWithEventNoDispatch "SwitchCell_OnChangedFn" SwitchCell.OnProperty (fun target -> (target :?> SwitchCell).OnChanged)

let Text = Attributes.defineBindableWithEquality SwitchCell.TextProperty

@@ -26,10 +29,21 @@ module SwitchCellBuilders =
/// <param name="text">The text value</param>
/// <param name="value">The toggle state value</param>
/// <param name="onChanged">Change callback</param>
static member inline SwitchCell<'msg>(text: string, value: bool, onChanged: bool -> 'msg) =
static member inline SwitchCell(text: string, value: bool, onChanged: bool -> 'msg) =
WidgetBuilder<'msg, IFabSwitchCell>(
SwitchCell.WidgetKey,
SwitchCell.OnWithEventMsg.WithValue(MsgValueEventData.create value (fun (args: ToggledEventArgs) -> onChanged args.Value)),
SwitchCell.Text.WithValue(text)
)

/// <summary>Create a SwitchCell with a text, a toggle state, and listen to toggle state changes</summary>
/// <param name="text">The text value</param>
/// <param name="value">The toggle state value</param>
/// <param name="onChanged">Change callback</param>
static member inline SwitchCell(text: string, value: bool, onChanged: bool -> unit) =
WidgetBuilder<'msg, IFabSwitchCell>(
SwitchCell.WidgetKey,
SwitchCell.OnWithEvent.WithValue(ValueEventData.create value (fun (args: ToggledEventArgs) -> onChanged args.Value)),
SwitchCell.OnWithEventFn.WithValue(ValueEventData.create value (fun (args: ToggledEventArgs) -> onChanged args.Value)),
SwitchCell.Text.WithValue(text)
)

2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Cells/TextCell.fs
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ module TextCellBuilders =

/// <summary>Create a TextCell widget with a text</summary>
/// <param name="text">The text value</param>
static member inline TextCell<'msg>(text: string) =
static member inline TextCell(text: string) =
WidgetBuilder<'msg, IFabTextCell>(TextCell.WidgetKey, TextCell.Text.WithValue(text))

[<Extension>]
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Cells/ViewCell.fs
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ module ViewCellBuilders =

/// <summary>Create a ViewCell with a content widget</summary>
/// <param name="view">The content widget</param>
static member inline ViewCell<'msg, 'marker when 'marker :> IFabView>(view: WidgetBuilder<'msg, 'marker>) =
static member inline ViewCell(view: WidgetBuilder<'msg, #IFabView>) =
WidgetHelpers.buildWidgets<'msg, IFabViewCell> ViewCell.WidgetKey [| ViewCell.View.WithValue(view.Compile()) |]

[<Extension>]
52 changes: 41 additions & 11 deletions src/Fabulous.MauiControls/Views/Cells/_Cell.fs
Original file line number Diff line number Diff line change
@@ -9,11 +9,17 @@ type IFabCell =
inherit IFabElement

module Cell =
let Appearing =
Attributes.defineEventNoArg "Cell_Appearing" (fun target -> (target :?> Cell).Appearing)
let AppearingMsg =
Attributes.defineEventNoArg "Cell_AppearingMsg" (fun target -> (target :?> Cell).Appearing)

let AppearingFn =
Attributes.defineEventNoArgNoDispatch "Cell_AppearingFn" (fun target -> (target :?> Cell).Appearing)

let Disappearing =
Attributes.defineEventNoArg "Cell_Disappearing" (fun target -> (target :?> Cell).Disappearing)
let DisappearingMsg =
Attributes.defineEventNoArg "Cell_DisappearingMsg" (fun target -> (target :?> Cell).Disappearing)

let DisappearingFn =
Attributes.defineEventNoArgNoDispatch "Cell_DisappearingFn" (fun target -> (target :?> Cell).Disappearing)

let Height =
Attributes.defineFloat "Cell_Height" (fun _ newValueOpt node ->
@@ -28,8 +34,11 @@ module Cell =

let IsEnabled = Attributes.defineBindableBool Cell.IsEnabledProperty

let Tapped =
Attributes.defineEventNoArg "Cell_Tapped" (fun target -> (target :?> Cell).Tapped)
let TappedMsg =
Attributes.defineEventNoArg "Cell_TappedMsg" (fun target -> (target :?> Cell).Tapped)

let TappedFn =
Attributes.defineEventNoArgNoDispatch "Cell_TappedFn" (fun target -> (target :?> Cell).Tapped)

let ContextActions =
Attributes.defineListWidgetCollection "Cell_ContextActions" (fun target -> (target :?> Cell).ContextActions)
@@ -55,32 +64,53 @@ type CellModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onAppearing(this: WidgetBuilder<'msg, #IFabCell>, msg: 'msg) =
this.AddScalar(Cell.Appearing.WithValue(MsgValue(msg)))
this.AddScalar(Cell.AppearingMsg.WithValue(MsgValue(msg)))

/// <summary>Listen to the Appearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onAppearing(this: WidgetBuilder<'msg, #IFabCell>, fn: unit -> unit) =
this.AddScalar(Cell.AppearingFn.WithValue(fn))

/// <summary>Listen to the Disappearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onDisappearing(this: WidgetBuilder<'msg, #IFabCell>, msg: 'msg) =
this.AddScalar(Cell.Disappearing.WithValue(MsgValue(msg)))
this.AddScalar(Cell.DisappearingMsg.WithValue(MsgValue(msg)))

/// <summary>Listen to the Disappearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onDisappearing(this: WidgetBuilder<'msg, #IFabCell>, fn: unit -> unit) =
this.AddScalar(Cell.DisappearingFn.WithValue(fn))

/// <summary>Listen to the Tapped event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onTapped(this: WidgetBuilder<'msg, #IFabCell>, msg: 'msg) =
this.AddScalar(Cell.Tapped.WithValue(MsgValue(msg)))
this.AddScalar(Cell.TappedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen to the Tapped event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onTapped(this: WidgetBuilder<'msg, #IFabCell>, fn: unit -> unit) =
this.AddScalar(Cell.TappedFn.WithValue(fn))

/// <summary>Set the context actions of the cell</summary>
/// <param name="this">Current widget</param>
[<Extension>]
static member inline contextActions<'msg, 'marker when 'marker :> IFabCell>(this: WidgetBuilder<'msg, 'marker>) =
static member inline contextActions<'msg, 'marker when 'msg : equality and 'marker :> IFabCell>(this: WidgetBuilder<'msg, 'marker>) =
WidgetHelpers.buildAttributeCollection<'msg, 'marker, IFabMenuItem> Cell.ContextActions this

[<Extension>]
type CellYieldExtensions =
[<Extension>]
static member inline Yield<'msg, 'marker, 'itemType when 'marker :> IFabCell and 'itemType :> IFabMenuItem>
static member inline Yield<'msg, 'marker, 'itemType when 'msg : equality and 'marker :> IFabCell and 'itemType :> IFabMenuItem>
(
_: AttributeCollectionBuilder<'msg, 'marker, IFabMenuItem>,
x: WidgetBuilder<'msg, 'itemType>
3 changes: 1 addition & 2 deletions src/Fabulous.MauiControls/Views/Collections/CarouselView.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
namespace Fabulous.Maui

open System
open System.Runtime.CompilerServices
open Fabulous
open Microsoft.Maui
@@ -62,7 +61,7 @@ module CarouselViewBuilders =

/// <summary>Create a CarouselView widget with a list of items</summary>
/// <param name="items">The items list</param>
static member inline CarouselView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabView>(items: seq<'itemData>) =
static member inline CarouselView<'msg, 'itemData, 'itemMarker when 'msg : equality and 'itemMarker :> IFabView>(items: seq<'itemData>) =
WidgetHelpers.buildItems<'msg, IFabCarouselView, 'itemData, 'itemMarker> CarouselView.WidgetKey ItemsView.ItemsSource items

[<Extension>]
20 changes: 15 additions & 5 deletions src/Fabulous.MauiControls/Views/Collections/CollectionView.fs
Original file line number Diff line number Diff line change
@@ -48,8 +48,11 @@ module CollectionView =
let ItemSizingStrategy =
Attributes.defineBindableEnum<ItemSizingStrategy> CollectionView.ItemSizingStrategyProperty

let SelectionChanged =
Attributes.defineEvent<SelectionChangedEventArgs> "CollectionView_SelectionChanged" (fun target -> (target :?> CollectionView).SelectionChanged)
let SelectionChangedMsg =
Attributes.defineEvent<SelectionChangedEventArgs> "CollectionView_SelectionChangedMsg" (fun target -> (target :?> CollectionView).SelectionChanged)

let SelectionChangedFn =
Attributes.defineEventNoDispatch<SelectionChangedEventArgs> "CollectionView_SelectionChangedFn" (fun target -> (target :?> CollectionView).SelectionChanged)

let SelectionMode =
Attributes.defineBindableEnum<SelectionMode> CollectionView.SelectionModeProperty
@@ -60,13 +63,13 @@ module CollectionViewBuilders =

/// <summary>Create a CollectionView widget with a list of items</summary>
/// <param name="items">The items list</param>
static member inline CollectionView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabView>(items: seq<'itemData>) =
static member inline CollectionView<'msg, 'itemData, 'itemMarker when 'msg : equality and 'itemMarker :> IFabView>(items: seq<'itemData>) =
WidgetHelpers.buildItems<'msg, IFabCollectionView, 'itemData, 'itemMarker> CollectionView.WidgetKey ItemsView.ItemsSource items

/// <summary>Create a CollectionView widget with a list of grouped items</summary>
/// <param name="items">The grouped items list</param>
static member inline GroupedCollectionView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker
when 'itemMarker :> IFabView and 'groupMarker :> IFabView and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
when 'msg : equality and 'itemMarker :> IFabView and 'groupMarker :> IFabView and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
(items: seq<'groupData>)
=
WidgetHelpers.buildGroupItems<'msg, IFabCollectionView, 'groupData, 'itemData, 'groupMarker, 'itemMarker>
@@ -102,7 +105,14 @@ type CollectionViewModifiers =
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onSelectionChanged(this: WidgetBuilder<'msg, #IFabCollectionView>, fn: SelectionChangedEventArgs -> 'msg) =
this.AddScalar(CollectionView.SelectionChanged.WithValue(fun args -> fn args |> box))
this.AddScalar(CollectionView.SelectionChangedMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the SelectionChanged event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onSelectionChanged(this: WidgetBuilder<'msg, #IFabCollectionView>, fn: SelectionChangedEventArgs -> unit) =
this.AddScalar(CollectionView.SelectionChangedFn.WithValue(fn))

/// <summary>Set the selection mode</summary>
/// <param name="this">Current widget</param>
117 changes: 93 additions & 24 deletions src/Fabulous.MauiControls/Views/Collections/ListView.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
namespace Fabulous.Maui

open System
open System.Runtime.CompilerServices
open Fabulous
open Microsoft.Maui
@@ -54,23 +53,38 @@ module ListView =

let IsRefreshing = Attributes.defineBindableBool ListView.IsRefreshingProperty

let ItemAppearing =
Attributes.defineEvent<ItemVisibilityEventArgs> "ListView_ItemAppearing" (fun target -> (target :?> ListView).ItemAppearing)
let ItemAppearingMsg =
Attributes.defineEvent<ItemVisibilityEventArgs> "ListView_ItemAppearingMsg" (fun target -> (target :?> ListView).ItemAppearing)

let ItemDisappearing =
Attributes.defineEvent<ItemVisibilityEventArgs> "ListView_ItemDisappearing" (fun target -> (target :?> ListView).ItemDisappearing)
let ItemAppearingFn =
Attributes.defineEventNoDispatch<ItemVisibilityEventArgs> "ListView_ItemAppearingFn" (fun target -> (target :?> ListView).ItemAppearing)

let ItemSelected =
Attributes.defineEvent<SelectedItemChangedEventArgs> "ListView_ItemSelected" (fun target -> (target :?> ListView).ItemSelected)
let ItemDisappearingMsg =
Attributes.defineEvent<ItemVisibilityEventArgs> "ListView_ItemDisappearingMsg" (fun target -> (target :?> ListView).ItemDisappearing)

let ItemTapped =
Attributes.defineEvent<ItemTappedEventArgs> "ListView_ItemTapped" (fun target -> (target :?> ListView).ItemTapped)
let ItemDisappearingFn =
Attributes.defineEventNoDispatch<ItemVisibilityEventArgs> "ListView_ItemDisappearingFn" (fun target -> (target :?> ListView).ItemDisappearing)

let ItemSelectedMsg =
Attributes.defineEvent<SelectedItemChangedEventArgs> "ListView_ItemSelectedMsg" (fun target -> (target :?> ListView).ItemSelected)

let ItemSelectedFn =
Attributes.defineEventNoDispatch<SelectedItemChangedEventArgs> "ListView_ItemSelectedFn" (fun target -> (target :?> ListView).ItemSelected)

let ItemTappedMsg =
Attributes.defineEvent<ItemTappedEventArgs> "ListView_ItemTappedMsg" (fun target -> (target :?> ListView).ItemTapped)

let ItemTappedFn =
Attributes.defineEvent<ItemTappedEventArgs> "ListView_ItemTappedFn" (fun target -> (target :?> ListView).ItemTapped)

let RefreshControlColor =
Attributes.defineBindableColor ListView.RefreshControlColorProperty

let Refreshing =
Attributes.defineEventNoArg "ListView_Refreshing" (fun target -> (target :?> ListView).Refreshing)
let RefreshingMsg =
Attributes.defineEventNoArg "ListView_RefreshingMsg" (fun target -> (target :?> ListView).Refreshing)

let RefreshingFn =
Attributes.defineEventNoArgNoDispatch "ListView_RefreshingFn" (fun target -> (target :?> ListView).Refreshing)

let RowHeight = Attributes.defineBindableInt ListView.RowHeightProperty

@@ -82,11 +96,17 @@ module ListView =
let SeparatorVisibility =
Attributes.defineBindableEnum<SeparatorVisibility> ListView.SeparatorVisibilityProperty

let Scrolled =
Attributes.defineEvent<ScrolledEventArgs> "ListView_Scrolled" (fun target -> (target :?> ListView).Scrolled)
let ScrolledMsg =
Attributes.defineEvent<ScrolledEventArgs> "ListView_ScrolledMsg" (fun target -> (target :?> ListView).Scrolled)

let ScrolledFn =
Attributes.defineEventNoDispatch<ScrolledEventArgs> "ListView_ScrolledFn" (fun target -> (target :?> ListView).Scrolled)

let ScrollToRequested =
Attributes.defineEvent<ScrollToRequestedEventArgs> "ListView_ScrollToRequested" (fun target -> (target :?> ListView).ScrollToRequested)
let ScrollToRequestedMsg =
Attributes.defineEvent<ScrollToRequestedEventArgs> "ListView_ScrollToRequestedMsg" (fun target -> (target :?> ListView).ScrollToRequested)

let ScrollToRequestedFn =
Attributes.defineEventNoDispatch<ScrollToRequestedEventArgs> "ListView_ScrollToRequestedFn" (fun target -> (target :?> ListView).ScrollToRequested)

let VerticalScrollBarVisibility =
Attributes.defineBindableEnum<ScrollBarVisibility> ListView.VerticalScrollBarVisibilityProperty
@@ -97,13 +117,13 @@ module ListViewBuilders =

/// <summary>Create a ListView with a list of items</summary>
/// <param name="items">The items list</param>
static member inline ListView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IFabCell>(items: seq<'itemData>) =
static member inline ListView<'msg, 'itemData, 'itemMarker when 'msg : equality and 'itemMarker :> IFabCell>(items: seq<'itemData>) =
WidgetHelpers.buildItems<'msg, IFabListView, 'itemData, 'itemMarker> ListView.WidgetKey ItemsViewOfCell.ItemsSource items

/// <summary>Create a ListView with a list of grouped items</summary>
/// <param name="items">The grouped items list</param>
static member inline GroupedListView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker
when 'itemMarker :> IFabCell and 'groupMarker :> IFabCell and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
when 'msg : equality and 'itemMarker :> IFabCell and 'groupMarker :> IFabCell and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
(items: seq<'groupData>)
=
WidgetHelpers.buildGroupItemsNoFooter<'msg, IFabListView, 'groupData, 'itemData, 'groupMarker, 'itemMarker>
@@ -160,49 +180,98 @@ type ListViewModifiers =
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemAppearing(this: WidgetBuilder<'msg, #IFabListView>, fn: ItemVisibilityEventArgs -> 'msg) =
this.AddScalar(ListView.ItemAppearing.WithValue(fun args -> fn args |> box))
this.AddScalar(ListView.ItemAppearingMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the ItemAppearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemAppearing(this: WidgetBuilder<'msg, #IFabListView>, fn: ItemVisibilityEventArgs -> unit) =
this.AddScalar(ListView.ItemAppearingFn.WithValue(fn))

/// <summary>Listen for the ItemDisappearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemDisappearing(this: WidgetBuilder<'msg, #IFabListView>, fn: ItemVisibilityEventArgs -> 'msg) =
this.AddScalar(ListView.ItemDisappearing.WithValue(fun args -> fn args |> box))
this.AddScalar(ListView.ItemDisappearingMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the ItemDisappearing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemDisappearing(this: WidgetBuilder<'msg, #IFabListView>, fn: ItemVisibilityEventArgs -> unit) =
this.AddScalar(ListView.ItemDisappearingFn.WithValue(fn))

/// <summary>Listen for the ItemTapped event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemTapped(this: WidgetBuilder<'msg, #IFabListView>, fn: int -> 'msg) =
this.AddScalar(ListView.ItemTapped.WithValue(fun args -> fn args.ItemIndex |> box))
this.AddScalar(ListView.ItemTappedMsg.WithValue(fun args -> fn args.ItemIndex |> box))

/// <summary>Listen for the ItemTapped event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemTapped(this: WidgetBuilder<'msg, #IFabListView>, fn: int -> unit) =
this.AddScalar(ListView.ItemTappedFn.WithValue(fun args -> fn args.ItemIndex))

/// <summary>Listen for the ItemSelected event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemSelected(this: WidgetBuilder<'msg, #IFabListView>, fn: int -> 'msg) =
this.AddScalar(ListView.ItemSelected.WithValue(fun args -> fn args.SelectedItemIndex |> box))
this.AddScalar(ListView.ItemSelectedMsg.WithValue(fun args -> fn args.SelectedItemIndex |> box))

/// <summary>Listen for the ItemSelected event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onItemSelected(this: WidgetBuilder<'msg, #IFabListView>, fn: int -> unit) =
this.AddScalar(ListView.ItemSelectedFn.WithValue(fun args -> fn args.SelectedItemIndex))

/// <summary>Listen for the Refreshing event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onRefreshing(this: WidgetBuilder<'msg, #IFabListView>, msg: 'msg) =
this.AddScalar(ListView.Refreshing.WithValue(MsgValue(msg)))
this.AddScalar(ListView.RefreshingMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Refreshing event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onRefreshing(this: WidgetBuilder<'msg, #IFabListView>, fn: unit -> unit) =
this.AddScalar(ListView.RefreshingFn.WithValue(fn))

/// <summary>Listen for the Scrolled event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrolled(this: WidgetBuilder<'msg, #IFabListView>, fn: ScrolledEventArgs -> 'msg) =
this.AddScalar(ListView.Scrolled.WithValue(fun args -> fn args |> box))
this.AddScalar(ListView.ScrolledMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the Scrolled event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrolled(this: WidgetBuilder<'msg, #IFabListView>, fn: ScrolledEventArgs -> unit) =
this.AddScalar(ListView.ScrolledFn.WithValue(fn))

/// <summary>Listen for the ScrollToRequested event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IFabListView>, fn: ScrollToRequestedEventArgs -> 'msg) =
this.AddScalar(ListView.ScrollToRequested.WithValue(fun args -> fn args |> box))
this.AddScalar(ListView.ScrollToRequestedMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the ScrollToRequested event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IFabListView>, fn: ScrollToRequestedEventArgs -> unit) =
this.AddScalar(ListView.ScrollToRequestedFn.WithValue(fn))

/// <summary>Set the refresh control color</summary>
/// <param name="this">Current widget</param>
51 changes: 42 additions & 9 deletions src/Fabulous.MauiControls/Views/Collections/_ItemsView.fs
Original file line number Diff line number Diff line change
@@ -36,14 +36,23 @@ module ItemsView =
let RemainingItemsThreshold =
Attributes.defineBindableInt ItemsView.RemainingItemsThresholdProperty

let RemainingItemsThresholdReached =
Attributes.defineEventNoArg "ItemsView_RemainingItemsThresholdReached" (fun target -> (target :?> ItemsView).RemainingItemsThresholdReached)
let RemainingItemsThresholdReachedMsg =
Attributes.defineEventNoArg "ItemsView_RemainingItemsThresholdReachedMsg" (fun target -> (target :?> ItemsView).RemainingItemsThresholdReached)

let Scrolled =
Attributes.defineEvent<ItemsViewScrolledEventArgs> "ItemsView_Scrolled" (fun target -> (target :?> ItemsView).Scrolled)
let RemainingItemsThresholdReachedFn =
Attributes.defineEventNoArgNoDispatch "ItemsView_RemainingItemsThresholdReachedFn" (fun target -> (target :?> ItemsView).RemainingItemsThresholdReached)

let ScrollToRequested =
Attributes.defineEvent<ScrollToRequestEventArgs> "ItemsView_ScrolledRequested" (fun target -> (target :?> ItemsView).ScrollToRequested)
let ScrolledMsg =
Attributes.defineEvent<ItemsViewScrolledEventArgs> "ItemsView_ScrolledMsg" (fun target -> (target :?> ItemsView).Scrolled)

let ScrolledFn =
Attributes.defineEventNoDispatch<ItemsViewScrolledEventArgs> "ItemsView_ScrolledFn" (fun target -> (target :?> ItemsView).Scrolled)

let ScrollToRequestedMsg =
Attributes.defineEvent<ScrollToRequestEventArgs> "ItemsView_ScrolledRequestedMsg" (fun target -> (target :?> ItemsView).ScrollToRequested)

let ScrollToRequestedFn =
Attributes.defineEventNoDispatch<ScrollToRequestEventArgs> "ItemsView_ScrolledRequestedFn" (fun target -> (target :?> ItemsView).ScrollToRequested)

let VerticalScrollBarVisibility =
Attributes.defineBindableEnum<ScrollBarVisibility> ItemsView.VerticalScrollBarVisibilityProperty
@@ -76,14 +85,28 @@ type ItemsViewModifiers =
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrolled(this: WidgetBuilder<'msg, #IFabItemsView>, fn: ItemsViewScrolledEventArgs -> 'msg) =
this.AddScalar(ItemsView.Scrolled.WithValue(fun args -> fn args |> box))
this.AddScalar(ItemsView.ScrolledMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the Scrolled event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrolled(this: WidgetBuilder<'msg, #IFabItemsView>, fn: ItemsViewScrolledEventArgs -> unit) =
this.AddScalar(ItemsView.ScrolledFn.WithValue(fn))

/// <summary>Listen for the ScrollToRequested event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IFabItemsView>, fn: ScrollToRequestEventArgs -> 'msg) =
this.AddScalar(ItemsView.ScrollToRequested.WithValue(fun args -> fn args |> box))
this.AddScalar(ItemsView.ScrollToRequestedMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the ScrollToRequested event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IFabItemsView>, fn: ScrollToRequestEventArgs -> unit) =
this.AddScalar(ItemsView.ScrollToRequestedFn.WithValue(fn))

/// <summary>Set the threshold of items not yet visible in the list at which the RemainingItemsThresholdReached event will be fired</summary>
/// <param name="this">Current widget</param>
@@ -93,7 +116,17 @@ type ItemsViewModifiers =
static member inline remainingItemsThreshold(this: WidgetBuilder<'msg, #IFabItemsView>, value: int, onThresholdReached: 'msg) =
this
.AddScalar(ItemsView.RemainingItemsThreshold.WithValue(value))
.AddScalar(ItemsView.RemainingItemsThresholdReached.WithValue(MsgValue(onThresholdReached)))
.AddScalar(ItemsView.RemainingItemsThresholdReachedMsg.WithValue(MsgValue(onThresholdReached)))

/// <summary>Set the threshold of items not yet visible in the list at which the RemainingItemsThresholdReached event will be fired</summary>
/// <param name="this">Current widget</param>
/// <param name="value">The threshold of items not yet visible in the list</param>
/// <param name="onThresholdReached">Message to dispatch</param>
[<Extension>]
static member inline remainingItemsThreshold(this: WidgetBuilder<'msg, #IFabItemsView>, value: int, onThresholdReached: unit -> unit) =
this
.AddScalar(ItemsView.RemainingItemsThreshold.WithValue(value))
.AddScalar(ItemsView.RemainingItemsThresholdReachedFn.WithValue(onThresholdReached))

/// <summary>Set the visibility of the vertical scroll bar</summary>
/// <param name="this">Current widget</param>
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ module ActivityIndicatorBuilders =

/// <summary>Create an ActivityIndicator widget with a running state</summary>
/// <param name="isRunning">The running state</param>
static member inline ActivityIndicator<'msg>(isRunning: bool) =
static member inline ActivityIndicator(isRunning: bool) =
WidgetBuilder<'msg, IFabActivityIndicator>(ActivityIndicator.WidgetKey, ActivityIndicator.IsRunning.WithValue(isRunning))

[<Extension>]
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Controls/BoxView.fs
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ module BoxViewBuilders =

/// <summary>Create a BoxView widget with a color</summary>
/// <param name="color">The color value</param>
static member inline BoxView<'msg>(color: Color) =
static member inline BoxView(color: Color) =
WidgetBuilder<'msg, IFabBoxView>(BoxView.WidgetKey, BoxView.Color.WithValue(color))

[<Extension>]
46 changes: 33 additions & 13 deletions src/Fabulous.MauiControls/Views/Controls/Button.fs
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ module Button =
let CharacterSpacing =
Attributes.defineBindableFloat Button.CharacterSpacingProperty

let Clicked =
Attributes.defineEventNoArg "Button_Clicked" (fun target -> (target :?> Button).Clicked)
let ClickedMsg =
Attributes.defineEventNoArg "Button_ClickedMsg" (fun target -> (target :?> Button).Clicked)

let Clicked' =
Attributes.defineEventNoArgNoDispatch "Button_Clicked" (fun target -> (target :?> Button).Clicked)
let ClickedFn =
Attributes.defineEventNoArgNoDispatch "Button_ClickedFn" (fun target -> (target :?> Button).Clicked)

let ContentLayout =
Attributes.defineBindableWithEquality<Button.ButtonContentLayout> Button.ContentLayoutProperty
@@ -51,11 +51,17 @@ module Button =
let Padding =
Attributes.defineBindableWithEquality<Thickness> Button.PaddingProperty

let Pressed =
Attributes.defineEventNoArg "Button_Pressed" (fun target -> (target :?> Button).Pressed)
let PressedMsg =
Attributes.defineEventNoArg "Button_PressedMsg" (fun target -> (target :?> Button).Pressed)

let Released =
Attributes.defineEventNoArg "Button_Released" (fun target -> (target :?> Button).Released)
let PressedFn =
Attributes.defineEventNoArgNoDispatch "Button_PressedFn" (fun target -> (target :?> Button).Pressed)

let ReleasedMsg =
Attributes.defineEventNoArg "Button_ReleasedMsg" (fun target -> (target :?> Button).Released)

let ReleasedFn =
Attributes.defineEventNoArgNoDispatch "Button_ReleasedFn" (fun target -> (target :?> Button).Released)

let Text = Attributes.defineBindableWithEquality<string> Button.TextProperty

@@ -71,14 +77,14 @@ module ButtonBuilders =
/// <summary>Create a Button widget with a text and listen for the Click event</summary>
/// <param name="text">The button on the tex</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline Button<'msg>(text: string, onClicked: 'msg) =
WidgetBuilder<'msg, IFabButton>(Button.WidgetKey, Button.Text.WithValue(text), Button.Clicked.WithValue(MsgValue(onClicked)))
static member inline Button(text: string, onClicked: 'msg) =
WidgetBuilder<'msg, IFabButton>(Button.WidgetKey, Button.Text.WithValue(text), Button.ClickedMsg.WithValue(MsgValue(onClicked)))

/// <summary>Create a Button widget with a text and listen for the Click event</summary>
/// <param name="text">The button on the tex</param>
/// <param name="onClicked">Function to execute</param>
static member inline Button(text: string, onClicked: unit -> unit) =
WidgetBuilder<unit, IFabButton>(Button.WidgetKey, Button.Text.WithValue(text), Button.Clicked'.WithValue(onClicked))
WidgetBuilder<'msg, IFabButton>(Button.WidgetKey, Button.Text.WithValue(text), Button.ClickedFn.WithValue(onClicked))

[<Extension>]
type ButtonModifiers =
@@ -178,14 +184,28 @@ type ButtonModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onPressed(this: WidgetBuilder<'msg, #IFabButton>, msg: 'msg) =
this.AddScalar(Button.Pressed.WithValue(MsgValue(msg)))
this.AddScalar(Button.PressedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Pressed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onPressed(this: WidgetBuilder<'msg, #IFabButton>, fn: unit -> unit) =
this.AddScalar(Button.PressedFn.WithValue(fn))

/// <summary>Listen for the Released event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onReleased(this: WidgetBuilder<'msg, #IFabButton>, msg: 'msg) =
this.AddScalar(Button.Released.WithValue(MsgValue(msg)))
this.AddScalar(Button.ReleasedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Released event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onReleased(this: WidgetBuilder<'msg, #IFabButton>, fn: unit -> unit) =
this.AddScalar(Button.ReleasedFn.WithValue(fn))

/// <summary>Set the padding inside the button</summary>
/// <param name="this">Current widget</param>
20 changes: 16 additions & 4 deletions src/Fabulous.MauiControls/Views/Controls/CheckBox.fs
Original file line number Diff line number Diff line change
@@ -13,8 +13,11 @@ module CheckBox =

let Color = Attributes.defineBindableColor CheckBox.ColorProperty

let IsCheckedWithEvent =
Attributes.defineBindableWithEvent "CheckBox_CheckedChanged" CheckBox.IsCheckedProperty (fun target -> (target :?> CheckBox).CheckedChanged)
let IsCheckedWithEventMsg =
Attributes.defineBindableWithEvent "CheckBox_CheckedChangedMsg" CheckBox.IsCheckedProperty (fun target -> (target :?> CheckBox).CheckedChanged)

let IsCheckedWithEventFn =
Attributes.defineBindableWithEventNoDispatch "CheckBox_CheckedChangedFn" CheckBox.IsCheckedProperty (fun target -> (target :?> CheckBox).CheckedChanged)

[<AutoOpen>]
module CheckBoxBuilders =
@@ -23,10 +26,19 @@ module CheckBoxBuilders =
/// <summary>Create a CheckBox widget with a state and listen for state changes</summary>
/// <param name="isChecked">The state of the checkbox</param>
/// <param name="onCheckedChanged">Message to dispatch</param>
static member inline CheckBox<'msg>(isChecked: bool, onCheckedChanged: bool -> 'msg) =
static member inline CheckBox(isChecked: bool, onCheckedChanged: bool -> 'msg) =
WidgetBuilder<'msg, IFabCheckBox>(
CheckBox.WidgetKey,
CheckBox.IsCheckedWithEventMsg.WithValue(MsgValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onCheckedChanged args.Value))
)

/// <summary>Create a CheckBox widget with a state and listen for state changes</summary>
/// <param name="isChecked">The state of the checkbox</param>
/// <param name="onCheckedChanged">Message to dispatch</param>
static member inline CheckBox(isChecked: bool, onCheckedChanged: bool -> unit) =
WidgetBuilder<'msg, IFabCheckBox>(
CheckBox.WidgetKey,
CheckBox.IsCheckedWithEvent.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onCheckedChanged args.Value))
CheckBox.IsCheckedWithEventFn.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onCheckedChanged args.Value))
)

[<Extension>]
73 changes: 68 additions & 5 deletions src/Fabulous.MauiControls/Views/Controls/DatePicker.fs
Original file line number Diff line number Diff line change
@@ -16,16 +16,16 @@ module DatePicker =
let CharacterSpacing =
Attributes.defineBindableFloat DatePicker.CharacterSpacingProperty

let DateWithEvent =
let name = "DatePicker_DateSelected"
let DateWithEventMsg =
let name = "DatePicker_DateSelectedMsg"
let minProperty = DatePicker.MinimumDateProperty
let valueProperty = DatePicker.DateProperty
let maxProperty = DatePicker.MaximumDateProperty

let key =
ScalarAttributeDefinitions.SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun oldValueOpt (newValueOpt: ValueEventData<struct (DateTime * DateTime * DateTime), DateChangedEventArgs> voption) node ->
(fun oldValueOpt (newValueOpt: MsgValueEventData<struct (DateTime * DateTime * DateTime), DateChangedEventArgs> voption) node ->
let target = node.Target :?> DatePicker

match newValueOpt with
@@ -65,6 +65,56 @@ module DatePicker =
)
|> AttributeDefinitionStore.registerScalar

{ Key = key; Name = name }
: ScalarAttributeDefinitions.SimpleScalarAttributeDefinition<MsgValueEventData<struct (DateTime * DateTime * DateTime), DateChangedEventArgs>>

let DateWithEventFn =
let name = "DatePicker_DateSelectedFn"
let minProperty = DatePicker.MinimumDateProperty
let valueProperty = DatePicker.DateProperty
let maxProperty = DatePicker.MaximumDateProperty

let key =
ScalarAttributeDefinitions.SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun oldValueOpt (newValueOpt: ValueEventData<struct (DateTime * DateTime * DateTime), DateChangedEventArgs> voption) node ->
let target = node.Target :?> DatePicker

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(minProperty)
target.ClearValue(maxProperty)
target.ClearValue(valueProperty)

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

// Set the new value
let struct (min, max, value) = curr.Value
target.SetValue(minProperty, min)
target.SetValue(maxProperty, max)
target.SetValue(valueProperty, value)

// Set the new event handler
let handler =
target.DateSelected.Subscribe(curr.Event)

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

{ Key = key; Name = name }
: ScalarAttributeDefinitions.SimpleScalarAttributeDefinition<ValueEventData<struct (DateTime * DateTime * DateTime), DateChangedEventArgs>>

@@ -104,10 +154,23 @@ module DatePickerBuilders =
/// <param name="max">The maximum date allowed</param>
/// <param name="date">The selected date</param>
/// <param name="onDateSelected">Message to dispatch</param>
static member inline DatePicker<'msg>(min: DateTime, max: DateTime, date: DateTime, onDateSelected: DateTime -> 'msg) =
static member inline DatePicker(min: DateTime, max: DateTime, date: DateTime, onDateSelected: DateTime -> 'msg) =
WidgetBuilder<'msg, IFabDatePicker>(
DatePicker.WidgetKey,
DatePicker.DateWithEventMsg.WithValue(
MsgValueEventData.create (struct (min, max, date)) (fun (args: DateChangedEventArgs) -> onDateSelected args.NewDate)
)
)

/// <summary>Create a DatePicker widget with a date, min-max bounds and listen for the date changes</summary>
/// <param name="min">The minimum date allowed</param>
/// <param name="max">The maximum date allowed</param>
/// <param name="date">The selected date</param>
/// <param name="onDateSelected">Message to dispatch</param>
static member inline DatePicker(min: DateTime, max: DateTime, date: DateTime, onDateSelected: DateTime -> unit) =
WidgetBuilder<'msg, IFabDatePicker>(
DatePicker.WidgetKey,
DatePicker.DateWithEvent.WithValue(
DatePicker.DateWithEventFn.WithValue(
ValueEventData.create (struct (min, max, date)) (fun (args: DateChangedEventArgs) -> onDateSelected args.NewDate)
)
)
29 changes: 24 additions & 5 deletions src/Fabulous.MauiControls/Views/Controls/Editor.fs
Original file line number Diff line number Diff line change
@@ -14,8 +14,11 @@ module Editor =
let AutoSize =
Attributes.defineBindableEnum<EditorAutoSizeOption> Editor.AutoSizeProperty

let Completed =
Attributes.defineEventNoArg "Editor_Completed" (fun target -> (target :?> Editor).Completed)
let CompletedMsg =
Attributes.defineEventNoArg "Editor_CompletedMsg" (fun target -> (target :?> Editor).Completed)

let CompletedFn =
Attributes.defineEventNoArgNoDispatch "Editor_CompletedFn" (fun target -> (target :?> Editor).Completed)

let CursorPosition = Attributes.defineBindableInt Editor.CursorPositionProperty

@@ -48,10 +51,19 @@ module EditorBuilders =
/// <summary>Create an Editor widget with a text and listen for text changes</summary>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline Editor<'msg>(text: string, onTextChanged: string -> 'msg) =
static member inline Editor(text: string, onTextChanged: string -> 'msg) =
WidgetBuilder<'msg, IFabEditor>(
Editor.WidgetKey,
InputView.TextWithEventMsg.WithValue(MsgValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

/// <summary>Create an Editor widget with a text and listen for text changes</summary>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline Editor(text: string, onTextChanged: string -> unit) =
WidgetBuilder<'msg, IFabEditor>(
Editor.WidgetKey,
InputView.TextWithEvent.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
InputView.TextWithEventFn.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

[<Extension>]
@@ -125,7 +137,14 @@ type EditorModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEditor>, msg: 'msg) =
this.AddScalar(Editor.Completed.WithValue(MsgValue(msg)))
this.AddScalar(Editor.CompletedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Completed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEditor>, fn: unit -> unit) =
this.AddScalar(Editor.CompletedFn.WithValue(fn))

/// <summary>Set the selection length</summary>
/// <param name="this">Current widget</param>
29 changes: 24 additions & 5 deletions src/Fabulous.MauiControls/Views/Controls/Entry.fs
Original file line number Diff line number Diff line change
@@ -16,8 +16,11 @@ module Entry =
let ClearButtonVisibility =
Attributes.defineBindableEnum<ClearButtonVisibility> Entry.ClearButtonVisibilityProperty

let Completed =
Attributes.defineEventNoArg "Entry_Completed" (fun target -> (target :?> Entry).Completed)
let CompletedMsg =
Attributes.defineEventNoArg "Entry_CompletedMsg" (fun target -> (target :?> Entry).Completed)

let CompletedFn =
Attributes.defineEventNoArgNoDispatch "Entry_CompletedFn" (fun target -> (target :?> Entry).Completed)

let CursorPosition = Attributes.defineBindableInt Entry.CursorPositionProperty

@@ -66,10 +69,19 @@ module EntryBuilders =
/// <summary>Create an Entry widget with a text and listen for text changes</summary>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline Entry<'msg>(text: string, onTextChanged: string -> 'msg) =
static member inline Entry(text: string, onTextChanged: string -> 'msg) =
WidgetBuilder<'msg, IFabEntry>(
Entry.WidgetKey,
InputView.TextWithEventMsg.WithValue(MsgValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

/// <summary>Create an Entry widget with a text and listen for text changes</summary>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
static member inline Entry(text: string, onTextChanged: string -> unit) =
WidgetBuilder<'msg, IFabEntry>(
Entry.WidgetKey,
InputView.TextWithEvent.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
InputView.TextWithEventFn.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue))
)

[<Extension>]
@@ -150,7 +162,14 @@ type EntryModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEntry>, msg: 'msg) =
this.AddScalar(Entry.Completed.WithValue(MsgValue(msg)))
this.AddScalar(Entry.CompletedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Completed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onCompleted(this: WidgetBuilder<'msg, #IFabEntry>, fn: unit -> unit) =
this.AddScalar(Entry.CompletedFn.WithValue(fn))

/// <summary>Set the return type of the keyboard</summary>
/// <param name="this">Current widget</param>
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Controls/FormattedLabel.fs
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ module FormattedLabelBuilders =
type Fabulous.Maui.View with

/// <summary>Create a FormattedLabel widget</summary>
static member inline FormattedLabel<'msg>() =
static member inline FormattedLabel() =
CollectionBuilder<'msg, IFabFormattedLabel, IFabSpan>(FormattedLabel.WidgetKey, FormattedLabel.Spans)

[<Extension>]
114 changes: 92 additions & 22 deletions src/Fabulous.MauiControls/Views/Controls/GraphicsView.fs
Original file line number Diff line number Diff line change
@@ -11,29 +11,50 @@ type IGraphicsView =
module GraphicsView =
let WidgetKey = Widgets.register<GraphicsView>()

let CancelInteraction =
Attributes.defineEventNoArg "GraphicsView_CancelInteraction" (fun target -> (target :?> GraphicsView).CancelInteraction)
let CancelInteractionMsg =
Attributes.defineEventNoArg "GraphicsView_CancelInteractionMsg" (fun target -> (target :?> GraphicsView).CancelInteraction)

let DragInteraction =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_DragInteraction" (fun target -> (target :?> GraphicsView).DragInteraction)
let CancelInteractionFn =
Attributes.defineEventNoArgNoDispatch "GraphicsView_CancelInteractionFn" (fun target -> (target :?> GraphicsView).CancelInteraction)

let DragInteractionMsg =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_DragInteractionMsg" (fun target -> (target :?> GraphicsView).DragInteraction)

let DragInteractionFn =
Attributes.defineEventNoDispatch<TouchEventArgs> "GraphicsView_DragInteractionFn" (fun target -> (target :?> GraphicsView).DragInteraction)

let Drawable =
Attributes.defineBindableWithEquality<IDrawable> GraphicsView.DrawableProperty

let EndHoverInteraction =
Attributes.defineEventNoArg "GraphicsView_EndHoverInteraction" (fun target -> (target :?> GraphicsView).EndHoverInteraction)
let EndHoverInteractionMsg =
Attributes.defineEventNoArg "GraphicsView_EndHoverInteractionMsg" (fun target -> (target :?> GraphicsView).EndHoverInteraction)

let EndHoverInteractionFn =
Attributes.defineEventNoArgNoDispatch "GraphicsView_EndHoverInteractionFn" (fun target -> (target :?> GraphicsView).EndHoverInteraction)

let EndInteractionMsg =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_EndInteractionMsg" (fun target -> (target :?> GraphicsView).EndInteraction)

let EndInteractionFn =
Attributes.defineEventNoDispatch<TouchEventArgs> "GraphicsView_EndInteractionFn" (fun target -> (target :?> GraphicsView).EndInteraction)

let EndInteraction =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_EndInteraction" (fun target -> (target :?> GraphicsView).EndInteraction)
let MoveHoverInteractionMsg =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_MoveHoverInteractionMsg" (fun target -> (target :?> GraphicsView).MoveHoverInteraction)

let MoveHoverInteraction =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_MoveHoverInteraction" (fun target -> (target :?> GraphicsView).MoveHoverInteraction)
let MoveHoverInteractionFn =
Attributes.defineEventNoDispatch<TouchEventArgs> "GraphicsView_MoveHoverInteractionFn" (fun target -> (target :?> GraphicsView).MoveHoverInteraction)

let StartHoverInteraction =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_StartHoverInteraction" (fun target -> (target :?> GraphicsView).StartHoverInteraction)
let StartHoverInteractionMsg =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_StartHoverInteractionMsg" (fun target -> (target :?> GraphicsView).StartHoverInteraction)

let StartInteraction =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_StartInteraction" (fun target -> (target :?> GraphicsView).StartInteraction)
let StartHoverInteractionFn =
Attributes.defineEventNoDispatch<TouchEventArgs> "GraphicsView_StartHoverInteractionFn" (fun target -> (target :?> GraphicsView).StartHoverInteraction)

let StartInteractionMsg =
Attributes.defineEvent<TouchEventArgs> "GraphicsView_StartInteractionMsg" (fun target -> (target :?> GraphicsView).StartInteraction)

let StartInteractionFn =
Attributes.defineEventNoDispatch<TouchEventArgs> "GraphicsView_StartInteractionFn" (fun target -> (target :?> GraphicsView).StartInteraction)

[<AutoOpen>]
module GraphicsViewBuilders =
@@ -42,7 +63,7 @@ module GraphicsViewBuilders =

/// <summary>Create a GraphicsView widget with a drawable content</summary>
/// <param name="drawable">The drawable content</param>
static member inline GraphicsView<'msg>(drawable: IDrawable) =
static member inline GraphicsView(drawable: IDrawable) =
WidgetBuilder<'msg, IGraphicsView>(GraphicsView.WidgetKey, GraphicsView.Drawable.WithValue(drawable))

[<Extension>]
@@ -52,46 +73,95 @@ type GraphicsViewModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onCancelInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, msg: 'msg) =
this.AddScalar(GraphicsView.CancelInteraction.WithValue(MsgValue(msg)))
this.AddScalar(GraphicsView.CancelInteractionMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the CancelInteraction event, which is raised when the press that made contact with the GraphicsView loses contact</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onCancelInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: unit -> unit) =
this.AddScalar(GraphicsView.CancelInteractionFn.WithValue(fn))

/// <summary>Listen for the DragInteraction event, with TouchEventArgs, which is raised when the GraphicsView is dragged</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onDragInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> 'msg) =
this.AddScalar(GraphicsView.DragInteraction.WithValue(fun args -> fn args |> box))
this.AddScalar(GraphicsView.DragInteractionMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the DragInteraction event, with TouchEventArgs, which is raised when the GraphicsView is dragged</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onDragInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> unit) =
this.AddScalar(GraphicsView.DragInteractionFn.WithValue(fn))

/// <summary>Listen for the EndHoverInteraction event, which is raised when a pointer leaves the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onEndHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, msg: 'msg) =
this.AddScalar(GraphicsView.EndHoverInteraction.WithValue(MsgValue(msg)))
this.AddScalar(GraphicsView.EndHoverInteractionMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the EndHoverInteraction event, which is raised when a pointer leaves the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onEndHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: unit -> unit) =
this.AddScalar(GraphicsView.EndHoverInteractionFn.WithValue(fn))

/// <summary>Listen for the EndInteraction event, with TouchEventArgs, which is raised when the press that raised the StartInteraction event is released</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onEndInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> 'msg) =
this.AddScalar(GraphicsView.EndInteraction.WithValue(fun args -> fn args |> box))
this.AddScalar(GraphicsView.EndInteractionMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the EndInteraction event, with TouchEventArgs, which is raised when the press that raised the StartInteraction event is released</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onEndInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> unit) =
this.AddScalar(GraphicsView.EndInteractionFn.WithValue(fn))

/// <summary>Listen for the MoveHoverInteraction event, with TouchEventArgs, which is raised when a pointer moves while the pointer remains within the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onMoveHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> 'msg) =
this.AddScalar(GraphicsView.MoveHoverInteraction.WithValue(fun args -> fn args |> box))
this.AddScalar(GraphicsView.MoveHoverInteractionMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the MoveHoverInteraction event, with TouchEventArgs, which is raised when a pointer moves while the pointer remains within the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onMoveHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> unit) =
this.AddScalar(GraphicsView.MoveHoverInteractionFn.WithValue(fn))

/// <summary>Listen for the StartHoverInteraction event, with TouchEventArgs, which is raised when a pointer enters the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onStartHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> 'msg) =
this.AddScalar(GraphicsView.StartHoverInteraction.WithValue(fun args -> fn args |> box))
this.AddScalar(GraphicsView.StartHoverInteractionMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the StartHoverInteraction event, with TouchEventArgs, which is raised when a pointer enters the hit test area of the GraphicsView</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onStartHoverInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> unit) =
this.AddScalar(GraphicsView.StartHoverInteractionFn.WithValue(fn))

/// <summary>Listen for the StartInteraction event, with TouchEventArgs, which is raised when the GraphicsView is pressed</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onStartInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> 'msg) =
this.AddScalar(GraphicsView.StartInteraction.WithValue(fun args -> fn args |> box))
this.AddScalar(GraphicsView.StartInteractionMsg.WithValue(fun args -> fn args |> box))

/// <summary>Listen for the StartInteraction event, with TouchEventArgs, which is raised when the GraphicsView is pressed</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Message to dispatch</param>
[<Extension>]
static member inline onStartInteraction(this: WidgetBuilder<'msg, #IGraphicsView>, fn: TouchEventArgs -> unit) =
this.AddScalar(GraphicsView.StartInteractionFn.WithValue(fn))
16 changes: 8 additions & 8 deletions src/Fabulous.MauiControls/Views/Controls/Image.fs
Original file line number Diff line number Diff line change
@@ -30,46 +30,46 @@ module ImageBuilders =

/// <summary>Create an Image widget with a source</summary>
/// <param name="source">The image source</param>
static member inline Image<'msg>(source: ImageSource) =
static member inline Image(source: ImageSource) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Source source))

/// <summary>Create an Image widget with a source and an aspect</summary>
/// <param name="source">The image source</param>
/// <param name="aspect">The image aspect</param>
static member inline Image<'msg>(source: ImageSource, aspect: Aspect) =
static member inline Image(source: ImageSource, aspect: Aspect) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Source source), Image.Aspect.WithValue(aspect))

/// <summary>Create an Image widget with a source</summary>
/// <param name="source">The image source</param>
static member inline Image<'msg>(source: string) =
static member inline Image(source: string) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.File source))

/// <summary>Create an Image widget with a source and an aspect</summary>
/// <param name="source">The image source</param>
/// <param name="aspect">The image aspect</param>
static member inline Image<'msg>(source: string, aspect: Aspect) =
static member inline Image(source: string, aspect: Aspect) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.File source), Image.Aspect.WithValue(aspect))

/// <summary>Create an Image widget with a source</summary>
/// <param name="source">The image source</param>
static member inline Image<'msg>(source: Uri) =
static member inline Image(source: Uri) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Uri source))

/// <summary>Create an Image widget with a source and an aspect</summary>
/// <param name="source">The image source</param>
/// <param name="aspect">The image aspect</param>
static member inline Image<'msg>(source: Uri, aspect: Aspect) =
static member inline Image(source: Uri, aspect: Aspect) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Uri source), Image.Aspect.WithValue(aspect))

/// <summary>Create an Image widget with a source</summary>
/// <param name="source">The image source</param>
static member inline Image<'msg>(source: Stream) =
static member inline Image(source: Stream) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Stream source))

/// <summary>Create an Image widget with a source and an aspect</summary>
/// <param name="source">The image source</param>
/// <param name="aspect">The image aspect</param>
static member inline Image<'msg>(source: Stream, aspect: Aspect) =
static member inline Image(source: Stream, aspect: Aspect) =
WidgetBuilder<'msg, IFabImage>(Image.WidgetKey, Image.Source.WithValue(ImageSourceValue.Stream source), Image.Aspect.WithValue(aspect))

[<Extension>]
159 changes: 135 additions & 24 deletions src/Fabulous.MauiControls/Views/Controls/ImageButton.fs
Original file line number Diff line number Diff line change
@@ -21,8 +21,11 @@ module ImageButton =

let BorderWidth = Attributes.defineBindableFloat ImageButton.BorderWidthProperty

let Clicked =
Attributes.defineEventNoArg "ImageButton_Clicked" (fun target -> (target :?> ImageButton).Clicked)
let ClickedMsg =
Attributes.defineEventNoArg "ImageButton_ClickedMsg" (fun target -> (target :?> ImageButton).Clicked)

let ClickedFn =
Attributes.defineEventNoArgNoDispatch "ImageButton_ClickedFn" (fun target -> (target :?> ImageButton).Clicked)

let CornerRadius = Attributes.defineBindableFloat ImageButton.CornerRadiusProperty

@@ -35,11 +38,17 @@ module ImageButton =
let Padding =
Attributes.defineBindableWithEquality<Thickness> ImageButton.PaddingProperty

let Pressed =
Attributes.defineEventNoArg "ImageButton_Pressed" (fun target -> (target :?> ImageButton).Pressed)
let PressedMsg =
Attributes.defineEventNoArg "ImageButton_PressedMsg" (fun target -> (target :?> ImageButton).Pressed)

let PressedFn =
Attributes.defineEventNoArgNoDispatch "ImageButton_PressedFn" (fun target -> (target :?> ImageButton).Pressed)

let ReleasedMsg =
Attributes.defineEventNoArg "ImageButton_ReleasedMsg" (fun target -> (target :?> ImageButton).Released)

let Released =
Attributes.defineEventNoArg "ImageButton_Released" (fun target -> (target :?> ImageButton).Released)
let ReleasedFn =
Attributes.defineEventNoArgNoDispatch "ImageButton_ReleasedFn" (fun target -> (target :?> ImageButton).Released)

let Source = Attributes.defineBindableImageSource ImageButton.SourceProperty

@@ -50,87 +59,175 @@ module ImageButtonBuilders =
/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton<'msg>(source: ImageSource, onClicked: 'msg) =
static member inline ImageButton(source: ImageSource, onClicked: 'msg) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Source source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton<'msg>(source: ImageSource, onClicked: 'msg, aspect: Aspect) =
static member inline ImageButton(source: ImageSource, onClicked: 'msg, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Source source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton<'msg>(source: string, onClicked: 'msg) =
static member inline ImageButton(source: string, onClicked: 'msg) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.File source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton<'msg>(source: string, onClicked: 'msg, aspect: Aspect) =
static member inline ImageButton(source: string, onClicked: 'msg, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.File source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton<'msg>(source: Uri, onClicked: 'msg) =
static member inline ImageButton(source: Uri, onClicked: 'msg) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Uri source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton<'msg>(source: Uri, onClicked: 'msg, aspect: Aspect) =
static member inline ImageButton(source: Uri, onClicked: 'msg, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Uri source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton<'msg>(source: Stream, onClicked: 'msg) =
static member inline ImageButton(source: Stream, onClicked: 'msg) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Stream source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton<'msg>(source: Stream, onClicked: 'msg, aspect: Aspect) =
static member inline ImageButton(source: Stream, onClicked: 'msg, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.Clicked.WithValue(MsgValue(onClicked)),
ImageButton.ClickedMsg.WithValue(MsgValue(onClicked)),
ImageButton.Source.WithValue(ImageSourceValue.Stream source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton(source: ImageSource, onClicked: unit -> unit) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Source source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton(source: ImageSource, onClicked: unit -> unit, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Source source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton(source: string, onClicked: unit -> unit) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.File source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton(source: string, onClicked: unit -> unit, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.File source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton(source: Uri, onClicked: unit -> unit) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Uri source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton(source: Uri, onClicked: unit -> unit, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Uri source),
ImageButton.Aspect.WithValue(aspect)
)

/// <summary>Create an ImageButton with an image source and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
static member inline ImageButton(source: Stream, onClicked: unit -> unit) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Stream source)
)

/// <summary>Create an ImageButton with an image source and an aspect and listen for the Click event</summary>
/// <param name="source">The image source</param>
/// <param name="onClicked">Message to dispatch</param>
/// <param name="aspect">The aspect value</param>
static member inline ImageButton(source: Stream, onClicked: unit -> unit, aspect: Aspect) =
WidgetBuilder<'msg, IFabImageButton>(
ImageButton.WidgetKey,
ImageButton.ClickedFn.WithValue(onClicked),
ImageButton.Source.WithValue(ImageSourceValue.Stream source),
ImageButton.Aspect.WithValue(aspect)
)
@@ -184,14 +281,28 @@ type ImageButtonModifiers =
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onPressed(this: WidgetBuilder<'msg, #IFabImageButton>, msg: 'msg) =
this.AddScalar(ImageButton.Pressed.WithValue(MsgValue(msg)))
this.AddScalar(ImageButton.PressedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Pressed event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onPressed(this: WidgetBuilder<'msg, #IFabImageButton>, fn: unit -> unit) =
this.AddScalar(ImageButton.PressedFn.WithValue(fn))

/// <summary>Listen for the Released event</summary>
/// <param name="this">Current widget</param>
/// <param name="msg">Message to dispatch</param>
[<Extension>]
static member inline onReleased(this: WidgetBuilder<'msg, #IFabImageButton>, msg: 'msg) =
this.AddScalar(ImageButton.Released.WithValue(MsgValue(msg)))
this.AddScalar(ImageButton.ReleasedMsg.WithValue(MsgValue(msg)))

/// <summary>Listen for the Released event</summary>
/// <param name="this">Current widget</param>
/// <param name="fn">Function to execute</param>
[<Extension>]
static member inline onReleased(this: WidgetBuilder<'msg, #IFabImageButton>, fn: unit -> unit) =
this.AddScalar(ImageButton.ReleasedFn.WithValue(fn))

/// <summary>Set the padding inside the button</summary>
/// <param name="this">Current widget</param>
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Controls/IndicatorView.fs
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ module IndicatorViewBuilders =
type Fabulous.Maui.View with

/// <summary>Create an IndicatorView widget with a reference</summary>
static member inline IndicatorView<'msg>(reference: ViewRef<IndicatorView>) =
static member inline IndicatorView(reference: ViewRef<IndicatorView>) =
WidgetBuilder<'msg, IFabIndicatorView>(IndicatorView.WidgetKey, ViewRefAttributes.ViewRef.WithValue(reference.Unbox))

[<Extension>]
2 changes: 1 addition & 1 deletion src/Fabulous.MauiControls/Views/Controls/Label.fs
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ module LabelBuilders =

/// <summary>Create a Label widget with a text</summary>
/// <param name="text">The text value</param>
static member inline Label<'msg>(text: string) =
static member inline Label(text: string) =
WidgetBuilder<'msg, IFabLabel>(Label.WidgetKey, Label.Text.WithValue(text))

[<Extension>]
25 changes: 21 additions & 4 deletions src/Fabulous.MauiControls/Views/Controls/Picker.fs
Original file line number Diff line number Diff line change
@@ -67,8 +67,12 @@ module Picker =
| ValueNone -> target.ClearValue(Picker.ItemsSourceProperty)
| ValueSome value -> target.SetValue(Picker.ItemsSourceProperty, value))

let SelectedIndexWithEvent =
Attributes.defineBindableWithEvent "Picker_SelectedIndexChanged" Picker.SelectedIndexProperty (fun target ->
let SelectedIndexWithEventMsg =
Attributes.defineBindableWithEvent "Picker_SelectedIndexChangedMsg" Picker.SelectedIndexProperty (fun target ->
(target :?> FabPicker).CustomSelectedIndexChanged)

let SelectedIndexWithEventFn =
Attributes.defineBindableWithEventNoDispatch "Picker_SelectedIndexChangedFn" Picker.SelectedIndexProperty (fun target ->
(target :?> FabPicker).CustomSelectedIndexChanged)

let TextColor = Attributes.defineBindableColor Picker.TextColorProperty
@@ -100,11 +104,24 @@ module PickerBuilders =
/// <param name="items">The items list</param>
/// <param name="selectedIndex">The selected index</param>
/// <param name="onSelectedIndexChanged">Message to dispatch</param>
static member inline Picker<'msg>(items: string list, selectedIndex: int, onSelectedIndexChanged: int -> 'msg) =
static member inline Picker(items: string list, selectedIndex: int, onSelectedIndexChanged: int -> 'msg) =
WidgetBuilder<'msg, IFabPicker>(
Picker.WidgetKey,
Picker.ItemsSource.WithValue(Array.ofList items),
Picker.SelectedIndexWithEventMsg.WithValue(
MsgValueEventData.create selectedIndex (fun (args: PositionChangedEventArgs) -> onSelectedIndexChanged args.CurrentPosition)
)
)

/// <summary>Create a Picker widget with a list of items, the selected index and listen to the selected index changes</summary>
/// <param name="items">The items list</param>
/// <param name="selectedIndex">The selected index</param>
/// <param name="onSelectedIndexChanged">Message to dispatch</param>
static member inline Picker(items: string list, selectedIndex: int, onSelectedIndexChanged: int -> unit) =
WidgetBuilder<'msg, IFabPicker>(
Picker.WidgetKey,
Picker.ItemsSource.WithValue(Array.ofList items),
Picker.SelectedIndexWithEvent.WithValue(
Picker.SelectedIndexWithEventFn.WithValue(
ValueEventData.create selectedIndex (fun (args: PositionChangedEventArgs) -> onSelectedIndexChanged args.CurrentPosition)
)
)
4 changes: 2 additions & 2 deletions src/Fabulous.MauiControls/Views/Controls/ProgressBar.fs
Original file line number Diff line number Diff line change
@@ -38,14 +38,14 @@ module ProgressBarBuilders =

/// <summary>Create a ProgressBar widget with a current progress value</summary>
/// <param name="progress">The progress value</param>
static member inline ProgressBar<'msg>(progress: float) =
static member inline ProgressBar(progress: float) =
WidgetBuilder<'msg, IFabProgressBar>(ProgressBar.WidgetKey, ProgressBar.Progress.WithValue(progress))

/// <summary>Create a ProgressBar widget with a progress value that will animate when changed</summary>
/// <param name="progress">The progress value</param>
/// <param name="duration">The duration of the animation</param>
/// <param name="easing">The easing of the animation</param>
static member inline ProgressBar<'msg>(progress: float, duration: int, easing: Easing) =
static member inline ProgressBar(progress: float, duration: int, easing: Easing) =
WidgetBuilder<'msg, IFabProgressBar>(
ProgressBar.WidgetKey,
ProgressBarAnimations.ProgressTo.WithValue(
40 changes: 35 additions & 5 deletions src/Fabulous.MauiControls/Views/Controls/RadioButton.fs
Original file line number Diff line number Diff line change
@@ -41,8 +41,11 @@ module RadioButton =
let GroupName =
Attributes.defineBindableWithEquality<string> RadioButton.GroupNameProperty

let IsCheckedWithEvent =
Attributes.defineBindableWithEvent "RadioButton_CheckedChanged" RadioButton.IsCheckedProperty (fun target -> (target :?> RadioButton).CheckedChanged)
let IsCheckedWithEventMsg =
Attributes.defineBindableWithEvent "RadioButton_CheckedChangedMsg" RadioButton.IsCheckedProperty (fun target -> (target :?> RadioButton).CheckedChanged)

let IsCheckedWithEventFn =
Attributes.defineBindableWithEventNoDispatch "RadioButton_CheckedChangedFn" RadioButton.IsCheckedProperty (fun target -> (target :?> RadioButton).CheckedChanged)

let TextColor = Attributes.defineBindableColor RadioButton.TextColorProperty

@@ -61,10 +64,21 @@ module RadioButtonBuilders =
/// <param name="content">The content</param>
/// <param name="isChecked">The checked state</param>
/// <param name="onChecked">Message to dispatch</param>
static member inline RadioButton<'msg>(content: string, isChecked: bool, onChecked: bool -> 'msg) =
static member inline RadioButton(content: string, isChecked: bool, onChecked: bool -> 'msg) =
WidgetBuilder<'msg, IFabRadioButton>(
RadioButton.WidgetKey,
RadioButton.IsCheckedWithEventMsg.WithValue(MsgValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value)),
RadioButton.ContentString.WithValue(content)
)

/// <summary>Create a RadioButton widget with a content, a checked state and listen for the checked state changes</summary>
/// <param name="content">The content</param>
/// <param name="isChecked">The checked state</param>
/// <param name="onChecked">Message to dispatch</param>
static member inline RadioButton(content: string, isChecked: bool, onChecked: bool -> unit) =
WidgetBuilder<'msg, IFabRadioButton>(
RadioButton.WidgetKey,
RadioButton.IsCheckedWithEvent.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value)),
RadioButton.IsCheckedWithEventFn.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value)),
RadioButton.ContentString.WithValue(content)
)

@@ -77,7 +91,23 @@ module RadioButtonBuilders =
RadioButton.WidgetKey,
AttributesBundle(
StackList.one(
RadioButton.IsCheckedWithEvent.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value))
RadioButton.IsCheckedWithEventMsg.WithValue(MsgValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value))
),
ValueSome [| RadioButton.ContentWidget.WithValue(content.Compile()) |],
ValueNone
)
)

/// <summary>Create a RadioButton widget with a content, a checked state and listen for the checked state changes</summary>
/// <param name="content">The content widget</param>
/// <param name="isChecked">The checked state</param>
/// <param name="onChecked">Message to dispatch</param>
static member inline RadioButton(content: WidgetBuilder<'msg, #IFabView>, isChecked: bool, onChecked: bool -> unit) =
WidgetBuilder<'msg, IFabRadioButton>(
RadioButton.WidgetKey,
AttributesBundle(
StackList.one(
RadioButton.IsCheckedWithEventFn.WithValue(ValueEventData.create isChecked (fun (args: CheckedChangedEventArgs) -> onChecked args.Value))
),
ValueSome [| RadioButton.ContentWidget.WithValue(content.Compile()) |],
ValueNone
24 changes: 19 additions & 5 deletions src/Fabulous.MauiControls/Views/Controls/SearchBar.fs
Original file line number Diff line number Diff line change
@@ -34,8 +34,11 @@ module SearchBar =
let IsTextPredictionEnabled =
Attributes.defineBindableBool SearchBar.IsTextPredictionEnabledProperty

let SearchButtonPressed =
Attributes.defineEventNoArg "SearchBar_SearchButtonPressed" (fun target -> (target :?> SearchBar).SearchButtonPressed)
let SearchButtonPressedMsg =
Attributes.defineEventNoArg "SearchBar_SearchButtonPressedMsg" (fun target -> (target :?> SearchBar).SearchButtonPressed)

let SearchButtonPressedFn =
Attributes.defineEventNoArgNoDispatch "SearchBar_SearchButtonPressedFn" (fun target -> (target :?> SearchBar).SearchButtonPressed)

let SelectionLength = Attributes.defineBindableInt SearchBar.SelectionLengthProperty

@@ -50,11 +53,22 @@ module SearchBarBuilders =
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
/// <param name="onSearchButtonPressed">Message to dispatch</param>
static member inline SearchBar<'msg>(text: string, onTextChanged: string -> 'msg, onSearchButtonPressed: 'msg) =
static member inline SearchBar(text: string, onTextChanged: string -> 'msg, onSearchButtonPressed: 'msg) =
WidgetBuilder<'msg, IFabSearchBar>(
SearchBar.WidgetKey,
InputView.TextWithEventMsg.WithValue(MsgValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue)),
SearchBar.SearchButtonPressedMsg.WithValue(MsgValue(onSearchButtonPressed))
)

/// <summary>Create a SearchBar widget with a text and listen for both text changes and search button presses</summary>
/// <param name="text">The text value</param>
/// <param name="onTextChanged">Message to dispatch</param>
/// <param name="onSearchButtonPressed">Message to dispatch</param>
static member inline SearchBar(text: string, onTextChanged: string -> unit, onSearchButtonPressed: unit -> unit) =
WidgetBuilder<'msg, IFabSearchBar>(
SearchBar.WidgetKey,
InputView.TextWithEvent.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue)),
SearchBar.SearchButtonPressed.WithValue(MsgValue(onSearchButtonPressed))
InputView.TextWithEventFn.WithValue(ValueEventData.create text (fun (args: TextChangedEventArgs) -> onTextChanged args.NewTextValue)),
SearchBar.SearchButtonPressedFn.WithValue(onSearchButtonPressed)
)

[<Extension>]
Loading

0 comments on commit 8dd64e4

Please sign in to comment.