diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 93d31a9..d852aa2 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"fantomas": {
- "version": "5.1.4",
+ "version": "5.1.5",
"commands": [
"fantomas"
]
diff --git a/.editorconfig b/.editorconfig
index 7beb38c..cad3085 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,2 +1,3 @@
[*.fs]
+max_line_length=160
fsharp_space_before_lowercase_invocation = false
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..af8248a
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [TimLariviere, edgarfgp]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5768d19..12b1724 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,22 +1,28 @@
name: Build
on:
- push:
- branches: [ 'v2.1' ]
- paths-ignore: [ 'docs/**' ]
+ push:
+ branches: [ 'main' ]
+ paths-ignore: [ 'docs/**' ]
permissions: write-all
env:
- VERSION: 2.1.0
+ VERSION: 2.1.1
+ CONFIG: Release
+ SLN_FILE: Fabulous.MauiControls.NoSamples.sln
+ TEMPLATE_PROJ: templates/Fabulous.MauiControls.Templates.proj
jobs:
build:
runs-on: macos-12
- env:
- SLN_FILE: Fabulous.MauiControls.NoSamples.sln
- TEMPLATE_PROJ: templates/Fabulous.MauiControls.Templates.proj
steps:
- - uses: actions/checkout@v3
+ - name: Set nightly version
+ run: |
+ NIGHTLY_VERSION=${VERSION}-nightly-${GITHUB_RUN_ID}
+ echo "Nightly version is $NIGHTLY_VERSION"
+ echo "NIGHTLY_VERSION=$NIGHTLY_VERSION" >> "$GITHUB_ENV"
+ - name: Checkout sources
+ uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
@@ -26,14 +32,18 @@ jobs:
- name: Restore
run: dotnet restore ${SLN_FILE}
- name: Build
- run: dotnet build ${SLN_FILE} -p:Version=${VERSION}-nightly-${GITHUB_RUN_ID} --no-restore --configuration Release
+ run: dotnet build ${SLN_FILE} -p:Version=${NIGHTLY_VERSION} -c ${CONFIG} --no-restore
- name: Test
- run: dotnet test ${SLN_FILE} -p:Version=${VERSION}-nightly-${GITHUB_RUN_ID} --no-build --configuration Release
+ run: dotnet test ${SLN_FILE} -p:Version=${NIGHTLY_VERSION} -c ${CONFIG} --no-build
- name: Pack
run: |
- find templates -type f -name template.json | xargs sed -i bak "s/FABULOUS_PKG_VERSION/${VERSION}-nightly-${GITHUB_RUN_ID}/g"
- dotnet pack ${SLN_FILE} -p:Version=${VERSION}-nightly-${GITHUB_RUN_ID} --configuration Release --output nupkgs
- dotnet pack ${TEMPLATE_PROJ} -p:Version=${VERSION}-nightly-${GITHUB_RUN_ID} -p:IsNightlyBuild=true --configuration Release --output nupkgs
+ find templates -type f -name template.json | xargs sed -i bak "s/FABULOUS_PKG_VERSION/${NIGHTLY_VERSION}/g"
+ dotnet pack ${SLN_FILE} -p:Version=${NIGHTLY_VERSION} -c ${CONFIG} --no-build --output nupkgs
+ dotnet pack ${TEMPLATE_PROJ} -p:Version=${NIGHTLY_VERSION} -p:IsNightlyBuild=true -c ${CONFIG} --output nupkgs
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Packages
+ path: nupkgs/
- name: Push
- run: |
- dotnet nuget push "nupkgs/*.nupkg" -s https://nuget.pkg.github.com/fabulous-dev/index.json -k ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
+ run: dotnet nuget push "nupkgs/*.nupkg" -s https://nuget.pkg.github.com/fabulous-dev/index.json -k ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index e7f3490..25ed358 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -1,13 +1,16 @@
name: Pull Request
on: pull_request
+env:
+ SLN_FILE: Fabulous.MauiControls.NoSamples.sln
+ CONFIG: Release
+
jobs:
- build:
+ pull_request:
runs-on: macos-12
- env:
- SLN_FILE: Fabulous.MauiControls.NoSamples.sln
steps:
- - uses: actions/checkout@v3
+ - name: Checkout sources
+ uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
@@ -21,6 +24,6 @@ jobs:
- name: Restore
run: dotnet restore ${SLN_FILE}
- name: Build
- run: dotnet build ${SLN_FILE} --no-restore --configuration Release
+ run: dotnet build ${SLN_FILE} -c ${CONFIG} --no-restore
- name: Test
- run: dotnet test ${SLN_FILE} --no-build --configuration Release
+ run: dotnet test ${SLN_FILE} -c ${CONFIG} --no-build
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d4be7b8..7921846 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,16 +6,32 @@ on:
permissions: write-all
+env:
+ CONFIG: Release
+ SLN_FILE: Fabulous.MauiControls.NoSamples.sln
+ TEMPLATE_PROJ: templates/Fabulous.MauiControls.Templates.proj
+
jobs:
- build:
+ release:
runs-on: macos-12
environment: nuget
- env:
- SLN_FILE: Fabulous.MauiControls.NoSamples.sln
steps:
- - uses: actions/checkout@v3
+ - name: Checkout sources
+ uses: actions/checkout@v3
- name: Extract version from tag
uses: damienaicheh/extract-version-from-tag-action@v1.0.0
+ - name: Set release version
+ run: |
+ RELEASE_VERSION=${MAJOR}.${MINOR}.${PATCH}
+ echo "Release version is $RELEASE_VERSION"
+ echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_ENV"
+ - name: Get Changelog Entry
+ id: changelog_reader
+ uses: mindsers/changelog-reader-action@v2
+ with:
+ validation_level: warn
+ version: '${{ env.RELEASE_VERSION }}'
+ path: ./CHANGELOG.md
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
@@ -25,14 +41,18 @@ jobs:
- name: Restore
run: dotnet restore ${SLN_FILE}
- name: Build
- run: dotnet build ${SLN_FILE} -p:Version="${MAJOR}.${MINOR}.${PATCH}" --no-restore --configuration Release
+ run: dotnet build ${SLN_FILE} -p:Version=${RELEASE_VERSION} -c ${CONFIG} --no-restore
- name: Test
- run: dotnet test ${SLN_FILE} -p:Version="${MAJOR}.${MINOR}.${PATCH}" --no-build --configuration Release
+ run: dotnet test ${SLN_FILE} -p:Version=${RELEASE_VERSION} -c ${CONFIG} --no-build
- name: Pack
run: |
- find templates -type f -name template.json | xargs sed -i bak "s/FABULOUS_PKG_VERSION/${MAJOR}.${MINOR}.${PATCH}/g"
- dotnet pack ${SLN_FILE} -p:Version="${MAJOR}.${MINOR}.${PATCH}" --configuration Release --output nupkgs
- dotnet pack templates/Maui/Fabulous.MauiControls.Templates.proj -p:Version="${MAJOR}.${MINOR}.${PATCH}" --configuration Release --output nupkgs
+ find templates -type f -name template.json | xargs sed -i bak "s/FABULOUS_PKG_VERSION/${RELEASE_VERSION}/g"
+ dotnet pack ${SLN_FILE} -p:Version=${RELEASE_VERSION} -p:PackageReleaseNotes="${{ steps.changelog_reader.outputs.changes }}" -c ${CONFIG} --no-build --output nupkgs
+ dotnet pack ${TEMPLATE_PROJ} -p:Version=${RELEASE_VERSION} -c ${CONFIG} --output nupkgs
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Packages
+ path: nupkgs/
- name: Push
- run: |
- dotnet nuget push "nupkgs/*.nupkg" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_TOKEN }} --skip-duplicate
+ run: dotnet nuget push "nupkgs/*" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_TOKEN }} --skip-duplicate
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a4f7d4f..ca54461 100644
--- a/.gitignore
+++ b/.gitignore
@@ -351,4 +351,5 @@ MigrationBackup/
.idea
-Resource.designer.cs
\ No newline at end of file
+Resource.designer.cs
+nupkgs/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f7fa27..9627d8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,18 @@
-### Version 2.1.1
+# Changelog
-#### General
-- Fabulous.MauiControls has moved from the Fabulous repository to its own repository: [https://github.com/fabulous-dev/Fabulous.MauiControls](https://github.com/fabulous-dev/Fabulous.MauiControls)
\ No newline at end of file
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+_No unreleased changes_
+
+## [2.1.1] - 2023-01-05
+
+### Changed
+- Fabulous.MauiControls has moved from the Fabulous repository to its own repository: [https://github.com/fabulous-dev/Fabulous.MauiControls](https://github.com/fabulous-dev/Fabulous.MauiControls)
+
+[unreleased]: https://github.com/fabulous-dev/Fabulous.MauiControls/compare/2.1.1...HEAD
+[2.1.1]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/v2.1.1
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..1277f8b
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+maintainers@fabulous.dev.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..b1d9a24
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,21 @@
+
+
+
+ https://github.com/fabulous-dev/Fabulous.MauiControls
+ true
+ true
+ false
+
+
+
+
+ Fabulous contributors
+ Fabulous;F#;Declarative UI;MVU;Maui;Android;iOS;macOS;Windows;Linux
+ nuget-icon.png
+ README.md
+ Apache-2.0
+ https://github.com/fabulous-dev/Fabulous.MauiControls
+ snupkg
+
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..33da7f5
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,18 @@
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 74dd8bb..262b5cc 100644
--- a/README.md
+++ b/README.md
@@ -1,53 +1,74 @@
-#
+# Fabulous for .NET MAUI (Microsoft.Maui.Controls)
-*F# Functional App Development, using declarative dynamic UI.*
+[![build](https://img.shields.io/github/actions/workflow/status/fabulous-dev/Fabulous.MauiControls/build.yml?branch=main)](https://github.com/fabulous-dev/Fabulous.MauiControls/actions/workflows/build.yml) [![NuGet version](https://img.shields.io/nuget/v/Fabulous.MauiControls)](https://www.nuget.org/packages/Fabulous.MauiControls) [![NuGet downloads](https://img.shields.io/nuget/dt/Fabulous.MauiControls)](https://www.nuget.org/packages/Fabulous.MauiControls) [![Discord](https://img.shields.io/discord/716980335593914419?label=discord&logo=discord)](https://discord.gg/bpTJMbSSYK) [![Twitter Follow](https://img.shields.io/twitter/follow/FabulousAppDev?style=social)](https://twitter.com/FabulousAppDev)
-[![build](https://github.com/fabulous-dev/Fabulous.MauiControls/actions/workflows/build.yml/badge.svg)](https://github.com/fabulous-dev/Fabulous.MauiControls/actions/workflows/dotnet.yml) [![Fabulous.MauiControls NuGet version](https://badge.fury.io/nu/Fabulous.MauiControls.svg)](https://badge.fury.io/nu/Fabulous.MauiControls) [![Discord](https://img.shields.io/discord/716980335593914419?label=discord&logo=discord)](https://discord.gg/bpTJMbSSYK)
+Fabulous.MauiControls brings the great development experience of Fabulous to .NET MAUI, allowing you to take advantage of the latest cross-platform UI framework from Microsoft with a tailored declarative UI DSL and clean architecture.
-Never write a ViewModel class again! Conquer the world with clean dynamic UIs!
+Deploy to any platform supported by .NET MAUI, such as Android, iOS, macOS, Windows, Linux and more!
-Fabulous allows you to combine the power of functional programming and the simple Model-View-Update architecture to build any kind of mobile and desktop applications with an expressive, dynamic and clean UI DSL. Go cross-platform with Fabulous for Xamarin.Forms and target iOS, Android, Mac, WPF and more!
+```fs
+/// A simple Counter app
-## Documentation
+type Model =
+ { Count: int }
-Documentation is available at https://docs.fabulous.dev
+type Msg =
+ | Increment
+ | Decrement
-## About Fabulous
+let init () =
+ { Count = 0 }
-Fabulous aims to provide all the tools to let you create your own mobile and desktop apps using only F# and the [Model-View-Update architecture](https://guide.elm-lang.org/architecture/) (shorten to MVU), with a great F# DSL for building dynamic UIs.
-The combination of F# and MVU makes for a great development experience.
+let update msg model =
+ match msg with
+ | Increment -> { model with Count = model.Count + 1 }
+ | Decrement -> { model with Count = model.Count - 1 }
-Note that Fabulous itself does not provide UI controls, so you'll need to combine it with another framework like Microsoft.Maui.
+let view model =
+ Application(
+ ContentPage(
+ "Counter app",
+ VStack(spacing = 16.) {
+ Image(Aspect.AspectFit, "fabulous.png")
+
+ Label($"Count is {model.Count}")
+
+ Button("Increment", Increment)
+ Button("Decrement", Decrement)
+ }
+ )
+ )
+```
-### Fabulous for Maui.Controls
+To learn more about Fabulous, visit https://fabulous.dev.
-You will be able to combine MVU with an SwiftUI-inspired DSL in F#, while targeting several platforms (iOS, Android, and more) using the new single project format.
+### Getting Started
-With Fabulous for Maui.Controls, you will be able to write complete applications in F# like this:
-```fsharp
-type Model = { Text: string }
-type Msg = ButtonClicked
+You can start your new Fabulous.MauiControls app in a matter of seconds using the dotnet CLI templates.
+For a starter guide see our [documentation](https://docs.fabulous.dev/v2/maui.controls/getting-started).
-let init () = { Text = "Hello Fabulous!" }
+```sh
+dotnet new install Fabulous.MauiControls.Templates
+dotnet new fabulous-mauicontrols -n MyApp
+```
-let update msg model =
- match msg with
- | ButtonClicked -> { model with Text = "Thanks for using Fabulous!" }
+### Documentation
-let view model =
- Application() {
- NavigationPage() {
- ContentPage("Counter",
- VStack(spacing = 16.) {
- Image(Aspect.AspectFit, "fabulous.png")
- Label(model.Text)
- Button("Click me", ButtonClicked)
- }
- )
- }
- )
-```
+Documentation can be found at https://docs.fabulous.dev/v2/maui.controls
+
+### Sponsor Fabulous
+
+Donating is a fantastic way to support all the efforts going into making Fabulous the best declarative UI framework for dotnet.
+We accept donations through the GitHub Sponsors program.
+
+If you need support see Commercial Support section below.
+
+### Commercial support
+
+If you would like us to provide you with:
+
+- training and workshops,
+- support services,
+- and consulting services.
-## Credits
-This repository is inspired by [Elmish.WPF](https://github.com/Prolucid/Elmish.WPF), [Elmish.Forms](https://github.com/dboris/elmish-forms) and [elmish](https://github.com/elmish/elmish).
-
+Feel free to contact us: [support@fabulous.dev](mailto:support@fabulous.dev)
diff --git a/docs/2.x/apis/_index.md b/docs/v2/apis/_index.md
similarity index 100%
rename from docs/2.x/apis/_index.md
rename to docs/v2/apis/_index.md
diff --git a/docs/2.x/docs/_index.md b/docs/v2/docs/_index.md
similarity index 100%
rename from docs/2.x/docs/_index.md
rename to docs/v2/docs/_index.md
diff --git a/samples/CounterApp/App.fs b/samples/CounterApp/App.fs
index fcd82e9..4401790 100644
--- a/samples/CounterApp/App.fs
+++ b/samples/CounterApp/App.fs
@@ -7,9 +7,7 @@ open type Fabulous.Maui.View
module App =
type Model =
- { Count: int
- Step: int
- TimerOn: bool }
+ { Count: int; Step: int; TimerOn: bool }
type Msg =
| Increment
@@ -32,22 +30,14 @@ module App =
let update msg model =
match msg with
- | Increment ->
- { model with
- Count = model.Count + model.Step },
- Cmd.none
- | Decrement ->
- { model with
- Count = model.Count - model.Step },
- Cmd.none
+ | Increment -> { model with Count = model.Count + model.Step }, Cmd.none
+ | Decrement -> { model with Count = model.Count - model.Step }, Cmd.none
| Reset -> initModel, Cmd.none
| SetStep n -> { model with Step = int(n + 0.5) }, Cmd.none
| TimerToggled on -> { model with TimerOn = on }, (if on then timerCmd() else Cmd.none)
| TimedTick ->
if model.TimerOn then
- { model with
- Count = model.Count + model.Step },
- timerCmd()
+ { model with Count = model.Count + model.Step }, timerCmd()
else
model, Cmd.none
@@ -66,17 +56,16 @@ module App =
Label("Timer")
Switch(model.TimerOn, TimerToggled)
- })
+ })
.padding(20.)
.centerHorizontal()
Slider(0.0, 10.0, double model.Step, SetStep)
- Label($"Step size: %d{model.Step}")
- .centerTextHorizontal()
+ Label($"Step size: %d{model.Step}").centerTextHorizontal()
Button("Reset", Reset)
- })
+ })
.center()
)
)
diff --git a/samples/CounterApp/CounterApp.fsproj b/samples/CounterApp/CounterApp.fsproj
index 4755224..6aaa07f 100644
--- a/samples/CounterApp/CounterApp.fsproj
+++ b/samples/CounterApp/CounterApp.fsproj
@@ -1,108 +1,107 @@
-
- net7.0-android;net7.0-ios;net7.0-maccatalyst
- $(TargetFrameworks);net7.0-windows10.0.19041.0
-
-
- Exe
- CounterApp
- true
- true
- false
- true
-
-
- CounterApp
-
-
- org.fabulous.maui.counterapp
- 9ba62055-caf0-4bc6-aba6-2958ce60e04f
-
-
- 1.0
- 1
-
- $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
-
- 14.2
- 14.0
- 21.0
- 10.0.17763.0
- 10.0.17763.0
- 6.5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MSBuild:Compile
- $(DefaultXamlRuntime)
-
-
- $(WindowsProjectFolder)App.xaml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ CounterApp
+ true
+ true
+ false
+
+
+ CounterApp
+
+
+ org.fabulous.maui.counterapp
+ 9ba62055-caf0-4bc6-aba6-2958ce60e04f
+
+
+ 1.0
+ 1
+
+ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
+
+ $(WindowsProjectFolder)App.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/CounterApp/Platforms/Android/MainActivity.fs b/samples/CounterApp/Platforms/Android/MainActivity.fs
index 9c32ac8..764c9c5 100644
--- a/samples/CounterApp/Platforms/Android/MainActivity.fs
+++ b/samples/CounterApp/Platforms/Android/MainActivity.fs
@@ -6,11 +6,12 @@ open Microsoft.Maui
[]
+ ConfigurationChanges =
+ (ConfigChanges.ScreenSize
+ ||| ConfigChanges.Orientation
+ ||| ConfigChanges.UiMode
+ ||| ConfigChanges.ScreenLayout
+ ||| ConfigChanges.SmallestScreenSize
+ ||| ConfigChanges.Density))>]
type MainActivity() =
inherit MauiAppCompatActivity()
diff --git a/samples/HelloWorld/App.fs b/samples/HelloWorld/App.fs
index af478f2..70bbf23 100644
--- a/samples/HelloWorld/App.fs
+++ b/samples/HelloWorld/App.fs
@@ -50,10 +50,7 @@ module App =
.centerTextHorizontal()
Label("Welcome to .NET Multi-platform App UI powered by Fabulous")
- .semantics(
- SemanticHeadingLevel.Level2,
- "Welcome to dot net Multi platform App U I powered by Fabulous"
- )
+ .semantics(SemanticHeadingLevel.Level2, "Welcome to dot net Multi platform App U I powered by Fabulous")
.font(size = 18.)
.centerTextHorizontal()
@@ -76,15 +73,12 @@ module App =
else
$"You wrote '{model.EnteredText}'"
- Label(userText)
- .semantics(description = userText)
- .centerHorizontal()
- })
+ Label(userText).semantics(description = userText).centerHorizontal()
+ })
.padding(Thickness(30., 0., 30., 0.))
.centerVertical()
)
)
)
- let program =
- Program.statefulWithCmdMsg init update view mapCmd
+ let program = Program.statefulWithCmdMsg init update view mapCmd
diff --git a/samples/HelloWorld/HelloWorld.fsproj b/samples/HelloWorld/HelloWorld.fsproj
index 053f005..f4a7360 100644
--- a/samples/HelloWorld/HelloWorld.fsproj
+++ b/samples/HelloWorld/HelloWorld.fsproj
@@ -1,105 +1,104 @@
-
- net7.0-android;net7.0-ios;net7.0-maccatalyst
- $(TargetFrameworks);net7.0-windows10.0.19041.0
-
-
- Exe
- HelloWorld
- true
- true
- false
- true
-
-
- HelloWorld
-
-
- org.fabulous.maui.helloworld
- 27711c6d-5019-4609-8803-38afe3bc0a31
-
-
- 1.0
- 1
-
- $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
-
- 14.2
- 14.0
- 21.0
- 10.0.17763.0
- 10.0.17763.0
- 6.5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MSBuild:Compile
- $(DefaultXamlRuntime)
-
-
- $(WindowsProjectFolder)App.xaml
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ HelloWorld
+ true
+ true
+ false
+
+
+ HelloWorld
+
+
+ org.fabulous.maui.helloworld
+ 27711c6d-5019-4609-8803-38afe3bc0a31
+
+
+ 1.0
+ 1
+
+ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
+
+ $(WindowsProjectFolder)App.xaml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/HelloWorld/Platforms/Android/MainActivity.fs b/samples/HelloWorld/Platforms/Android/MainActivity.fs
index 872f287..8ef0ca1 100644
--- a/samples/HelloWorld/Platforms/Android/MainActivity.fs
+++ b/samples/HelloWorld/Platforms/Android/MainActivity.fs
@@ -6,11 +6,12 @@ open Microsoft.Maui
[]
+ ConfigurationChanges =
+ (ConfigChanges.ScreenSize
+ ||| ConfigChanges.Orientation
+ ||| ConfigChanges.UiMode
+ ||| ConfigChanges.ScreenLayout
+ ||| ConfigChanges.SmallestScreenSize
+ ||| ConfigChanges.Density))>]
type MainActivity() =
inherit MauiAppCompatActivity()
diff --git a/samples/Playground/App.fs b/samples/Playground/App.fs
index 8a6cb43..480127e 100644
--- a/samples/Playground/App.fs
+++ b/samples/Playground/App.fs
@@ -27,7 +27,7 @@ module App =
let update msg model =
match msg with
| TextChanged _ -> model
- | FocusChanged (field, isFocused) ->
+ | FocusChanged(field, isFocused) ->
if isFocused then
{ model with Focus = Some field }
else
@@ -62,7 +62,7 @@ module App =
Button("Set focus on Entry2", SetFocus(Some Entry2))
Button("Set focus on Entry3", SetFocus(Some Entry3))
Button("Unfocus", SetFocus None)
- })
+ })
.margin(20.)
)
)
diff --git a/samples/Playground/Platforms/Android/MainActivity.fs b/samples/Playground/Platforms/Android/MainActivity.fs
index 434dfba..f240cb3 100644
--- a/samples/Playground/Platforms/Android/MainActivity.fs
+++ b/samples/Playground/Platforms/Android/MainActivity.fs
@@ -6,11 +6,12 @@ open Microsoft.Maui
[]
+ ConfigurationChanges =
+ (ConfigChanges.ScreenSize
+ ||| ConfigChanges.Orientation
+ ||| ConfigChanges.UiMode
+ ||| ConfigChanges.ScreenLayout
+ ||| ConfigChanges.SmallestScreenSize
+ ||| ConfigChanges.Density))>]
type MainActivity() =
inherit MauiAppCompatActivity()
diff --git a/samples/Playground/Playground.fsproj b/samples/Playground/Playground.fsproj
index 0123d33..3fd3554 100644
--- a/samples/Playground/Playground.fsproj
+++ b/samples/Playground/Playground.fsproj
@@ -1,105 +1,104 @@
-
- net7.0-android;net7.0-ios;net7.0-maccatalyst
- $(TargetFrameworks);net7.0-windows10.0.19041.0
-
-
- Exe
- Playground
- true
- true
- false
- true
-
-
- Playground
-
-
- org.fabulous.maui.playground
- 16754483-590d-47a9-a8f2-4724a9b77037
-
-
- 1.0
- 1
-
- $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
-
- 14.2
- 14.0
- 21.0
- 10.0.17763.0
- 10.0.17763.0
- 6.5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MSBuild:Compile
- $(DefaultXamlRuntime)
-
-
- $(WindowsProjectFolder)App.xaml
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ Playground
+ true
+ true
+ false
+
+
+ Playground
+
+
+ org.fabulous.maui.playground
+ 16754483-590d-47a9-a8f2-4724a9b77037
+
+
+ 1.0
+ 1
+
+ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
+
+ $(WindowsProjectFolder)App.xaml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/TicTacToe/App.fs b/samples/TicTacToe/App.fs
index 43e1abb..fdbff2b 100644
--- a/samples/TicTacToe/App.fs
+++ b/samples/TicTacToe/App.fs
@@ -13,6 +13,7 @@ open type Fabulous.Maui.View
type Player =
| X
| O
+
member p.Swap =
match p with
| X -> O
@@ -27,6 +28,7 @@ type Player =
type GameCell =
| Empty
| Full of Player
+
member x.CanPlay = (x = Empty)
/// Represents the result of a game
@@ -54,33 +56,33 @@ type Row = GameCell list
/// Represents the state of the game
type Model =
{
- // The current theme: Light or Dark modes
- Theme: AppTheme
+ // The current theme: Light or Dark modes
+ Theme: AppTheme
- /// Who is next to play
- NextUp: Player
+ /// Who is next to play
+ NextUp: Player
- /// The state of play on the board
- Board: Board
+ /// The state of play on the board
+ Board: Board
- /// The state of play on the board
- GameScore: int * int
+ /// The state of play on the board
+ GameScore: int * int
- /// The model occasionally includes things related to the view. In this case,
- /// we track the desired visual size of the board, to ensure a square, in response to
- /// updates telling us the overall allocated size.
- VisualBoardSize: double }
+ /// The model occasionally includes things related to the view. In this case,
+ /// we track the desired visual size of the board, to ensure a square, in response to
+ /// updates telling us the overall allocated size.
+ VisualBoardSize: double
+ }
/// The model, update and view content of the app. This is placed in an
/// independent model to facilitate unit testing.
module App =
let positions =
- [ for x in 0 .. 2 do
- for y in 0 .. 2 do
+ [ for x in 0..2 do
+ for y in 0..2 do
yield (x, y) ]
- let initialBoard =
- Map.ofList [ for p in positions -> p, Empty ]
+ let initialBoard = Map.ofList [ for p in positions -> p, Empty ]
let init () =
let size =
@@ -100,10 +102,10 @@ module App =
let lines =
[
// rows
- for row in 0 .. 2 do
+ for row in 0..2 do
yield [ (row, 0); (row, 1); (row, 2) ]
// columns
- for col in 0 .. 2 do
+ for col in 0..2 do
yield [ (0, col); (1, col); (2, col) ]
// diagonals
yield [ (0, 0); (1, 1); (2, 2) ]
@@ -114,33 +116,28 @@ module App =
/// Determine if a line is a winning line.
let getLineWinner line =
- if line
- |> List.forall
- (function
- | Full X -> true
- | _ -> false) then
+ if
+ line
+ |> List.forall (function
+ | Full X -> true
+ | _ -> false)
+ then
Some X
- elif line
- |> List.forall
- (function
- | Full O -> true
- | _ -> false) then
+ elif
+ line
+ |> List.forall (function
+ | Full O -> true
+ | _ -> false)
+ then
Some O
else
None
/// Determine the game result, if any.
let getGameResult model =
- match
- lines
- |> Seq.tryPick(getLine model.Board >> getLineWinner)
- with
+ match lines |> Seq.tryPick(getLine model.Board >> getLineWinner) with
| Some p -> Win p
- | _ ->
- if anyMoreMoves model then
- StillPlaying
- else
- Draw
+ | _ -> if anyMoreMoves model then StillPlaying else Draw
/// Get a message to show the current game result
let getMessage model =
@@ -155,8 +152,8 @@ module App =
| Play pos ->
let newModel =
{ model with
- Board = model.Board.Add(pos, Full model.NextUp)
- NextUp = model.NextUp.Swap }
+ Board = model.Board.Add(pos, Full model.NextUp)
+ NextUp = model.NextUp.Swap }
// Make an announcement in the middle of the game.
let result = getGameResult newModel
@@ -165,21 +162,17 @@ module App =
let x, y = newModel.GameScore
match result with
- | Win p ->
- { newModel with
- GameScore = (if p = X then (x + 1, y) else (x, y + 1)) }
+ | Win p -> { newModel with GameScore = (if p = X then (x + 1, y) else (x, y + 1)) }
| _ -> newModel
// Return the new model.
newModel2
| Restart ->
{ model with
- NextUp = X
- Board = initialBoard
- GameScore = (0, 0) }
- | VisualBoardSizeChanged size ->
- { model with
- VisualBoardSize = size - 40. }
+ NextUp = X
+ Board = initialBoard
+ GameScore = (0, 0) }
+ | VisualBoardSizeChanged size -> { model with VisualBoardSize = size - 40. }
| ThemeChanged theme -> { model with Theme = theme }
/// A helper to get the suffix used in the Xaml for a position on the board.
@@ -188,8 +181,7 @@ module App =
/// A condition used in the 'view' function to check if we can play in a cell.
/// The visual contents of a cell depends on this condition.
let canPlay model cell =
- (cell = Empty)
- && (getGameResult model = StillPlaying)
+ (cell = Empty) && (getGameResult model = StillPlaying)
/// The dynamic 'view' function giving the updated content for the view
let view model =
@@ -197,37 +189,20 @@ module App =
ContentPage(
"TicTacToe",
Grid(coldefs = [ Star ], rowdefs = [ Star; Auto; Auto ]) {
- (Grid(coldefs = [ Star
- Absolute 5.0
- Star
- Absolute 5.0
- Star ],
- rowdefs = [ Star
- Absolute 5.0
- Star
- Absolute 5.0
- Star ]) {
+ (Grid(coldefs = [ Star; Absolute 5.0; Star; Absolute 5.0; Star ], rowdefs = [ Star; Absolute 5.0; Star; Absolute 5.0; Star ]) {
let gridColor =
match model.Theme with
| AppTheme.Dark -> Controls.SolidColorBrush(Colors.White)
| _ -> Controls.SolidColorBrush(Colors.Black)
- Rectangle(5., gridColor)
- .gridRow(1)
- .gridColumnSpan(5)
+ Rectangle(5., gridColor).gridRow(1).gridColumnSpan(5)
- Rectangle(5., gridColor)
- .gridRow(3)
- .gridColumnSpan(5)
+ Rectangle(5., gridColor).gridRow(3).gridColumnSpan(5)
- Rectangle(5., gridColor)
- .gridColumn(1)
- .gridRowSpan(5)
+ Rectangle(5., gridColor).gridColumn(1).gridRowSpan(5)
- Rectangle(5., gridColor)
- .gridColumn(3)
- .gridRowSpan(5)
+ Rectangle(5., gridColor).gridColumn(3).gridRowSpan(5)
for row, col as pos in positions do
if canPlay model model.Board.[pos] then
@@ -253,18 +228,14 @@ module App =
.margin(10.)
.gridRow(row * 2)
.gridColumn(col * 2)
- })
+ })
.rowSpacing(0.)
.columnSpacing(0.)
.centerVertical()
.size(model.VisualBoardSize, model.VisualBoardSize)
.gridRow(0)
- Label(getMessage model)
- .font(size = 32.)
- .center()
- .margin(10.)
- .gridRow(1)
+ Label(getMessage model).font(size = 32.).center().margin(10.).gridRow(1)
Button("Restart game", Restart)
.textColor(Colors.Black.ToFabColor())
@@ -277,14 +248,11 @@ module App =
let program =
Program.stateful init update view
- |> Program.withSubscription
- (fun _ ->
- Cmd.ofSub
- (fun dispatch ->
- DeviceDisplay.MainDisplayInfoChanged.Add
- (fun args ->
- let size =
- System.Math.Min(args.DisplayInfo.Width, args.DisplayInfo.Height)
- / DeviceDisplay.MainDisplayInfo.Density
-
- dispatch(VisualBoardSizeChanged size))))
+ |> Program.withSubscription(fun _ ->
+ Cmd.ofSub(fun dispatch ->
+ DeviceDisplay.MainDisplayInfoChanged.Add(fun args ->
+ let size =
+ System.Math.Min(args.DisplayInfo.Width, args.DisplayInfo.Height)
+ / DeviceDisplay.MainDisplayInfo.Density
+
+ dispatch(VisualBoardSizeChanged size))))
diff --git a/samples/TicTacToe/Platforms/Android/MainActivity.fs b/samples/TicTacToe/Platforms/Android/MainActivity.fs
index 97d0e3c..1d21d8d 100644
--- a/samples/TicTacToe/Platforms/Android/MainActivity.fs
+++ b/samples/TicTacToe/Platforms/Android/MainActivity.fs
@@ -6,11 +6,12 @@ open Microsoft.Maui
[]
+ ConfigurationChanges =
+ (ConfigChanges.ScreenSize
+ ||| ConfigChanges.Orientation
+ ||| ConfigChanges.UiMode
+ ||| ConfigChanges.ScreenLayout
+ ||| ConfigChanges.SmallestScreenSize
+ ||| ConfigChanges.Density))>]
type MainActivity() =
inherit MauiAppCompatActivity()
diff --git a/samples/TicTacToe/TicTacToe.fsproj b/samples/TicTacToe/TicTacToe.fsproj
index e19cc0a..04caf5a 100644
--- a/samples/TicTacToe/TicTacToe.fsproj
+++ b/samples/TicTacToe/TicTacToe.fsproj
@@ -1,108 +1,107 @@
-
- net7.0-android;net7.0-ios;net7.0-maccatalyst
- $(TargetFrameworks);net7.0-windows10.0.19041.0
-
-
- Exe
- TicTacToe
- true
- true
- false
- true
-
-
- TicTacToe
-
-
- org.fabulous.maui.tictactoe
- 22582e08-ff98-48d9-bebe-1a28e36ab7e5
-
-
- 1.0
- 1
-
- $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
-
- 14.2
- 14.0
- 21.0
- 10.0.17763.0
- 10.0.17763.0
- 6.5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MSBuild:Compile
- $(DefaultXamlRuntime)
-
-
- $(WindowsProjectFolder)App.xaml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ TicTacToe
+ true
+ true
+ false
+
+
+ TicTacToe
+
+
+ org.fabulous.maui.tictactoe
+ 22582e08-ff98-48d9-bebe-1a28e36ab7e5
+
+
+ 1.0
+ 1
+
+ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
+
+ $(WindowsProjectFolder)App.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Fabulous.MauiControls.Tests/Fabulous.MauiControls.Tests.fsproj b/src/Fabulous.MauiControls.Tests/Fabulous.MauiControls.Tests.fsproj
index cb62e11..74dd000 100644
--- a/src/Fabulous.MauiControls.Tests/Fabulous.MauiControls.Tests.fsproj
+++ b/src/Fabulous.MauiControls.Tests/Fabulous.MauiControls.Tests.fsproj
@@ -1,16 +1,20 @@
-
net7.0
- true
+ Library
+ false
-
-
+
+
+
+
+
+
+
-
diff --git a/src/Fabulous.MauiControls.Tests/Library.fs b/src/Fabulous.MauiControls.Tests/Library.fs
index 686ee7e..29d75ba 100644
--- a/src/Fabulous.MauiControls.Tests/Library.fs
+++ b/src/Fabulous.MauiControls.Tests/Library.fs
@@ -1,5 +1,4 @@
-namespace Fabulous.MauiControls.Tests
-
-module Say =
- let hello name =
- printfn "Hello %s" name
+namespace Fabulous.MauiControls.Tests
+
+module Say =
+ let hello name = printfn "Hello %s" name
diff --git a/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs b/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs
index 493c4ce..a69bdc0 100644
--- a/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs
+++ b/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs
@@ -18,9 +18,7 @@ type AppHostBuilderExtensions =
this: MauiAppBuilder,
program: Program
) : MauiAppBuilder =
- this.UseMauiApp
- (fun (_serviceProvider: IServiceProvider) ->
- (Program.startApplication program) :> Microsoft.Maui.IApplication)
+ this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> (Program.startApplication program) :> Microsoft.Maui.IApplication)
[]
static member UseFabulousApp<'arg, 'model, 'msg, 'marker when 'marker :> Fabulous.Maui.IApplication>
@@ -29,6 +27,4 @@ type AppHostBuilderExtensions =
program: Program<'arg, 'model, 'msg, 'marker>,
arg: 'arg
) : MauiAppBuilder =
- this.UseMauiApp
- (fun (_serviceProvider: IServiceProvider) ->
- (Program.startApplicationWithArgs arg program) :> Microsoft.Maui.IApplication)
+ this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> (Program.startApplicationWithArgs arg program) :> Microsoft.Maui.IApplication)
diff --git a/src/Fabulous.MauiControls/Attributes.fs b/src/Fabulous.MauiControls/Attributes.fs
index 8e166f2..bd832f1 100644
--- a/src/Fabulous.MauiControls/Attributes.fs
+++ b/src/Fabulous.MauiControls/Attributes.fs
@@ -14,9 +14,9 @@ module AppTheme =
let inline create<'T when 'T: equality> (light: 'T) (dark: 'T option) =
{ Light = light
Dark =
- match dark with
- | None -> light
- | Some v -> v }
+ match dark with
+ | None -> light
+ | Some v -> v }
[]
type ValueEventData<'data, 'eventArgs> =
@@ -26,7 +26,7 @@ type ValueEventData<'data, 'eventArgs> =
module ValueEventData =
let create (value: 'data) (event: 'eventArgs -> obj) = { Value = value; Event = event }
-/// Xamarin Forms specific attributes that can be encoded as 8 bytes
+/// Maui.Controls specific attributes that can be encoded as 8 bytes
module SmallScalars =
module FabColor =
let inline encode (v: FabColor) : uint64 = SmallScalars.UInt.encode v.RGBA
@@ -38,17 +38,12 @@ module SmallScalars =
let inline encode (v: AppThemeValues) : uint64 =
let { Light = light; Dark = dark } = v
- ((FabColor.encode light) <<< 32)
- ||| (FabColor.encode dark)
+ ((FabColor.encode light) <<< 32) ||| (FabColor.encode dark)
let inline decode (encoded: uint64) : AppThemeValues =
- let light =
- ((encoded &&& 0xFFFFFFFF00000000UL) >>> 32)
- |> FabColor.decode
+ let light = ((encoded &&& 0xFFFFFFFF00000000UL) >>> 32) |> FabColor.decode
- let dark =
- (encoded &&& 0x00000000FFFFFFFFUL)
- |> FabColor.decode
+ let dark = (encoded &&& 0x00000000FFFFFFFFUL) |> FabColor.decode
{ Light = light; Dark = dark }
@@ -90,39 +85,30 @@ module Attributes =
([] convertValue: 'modelType -> 'valueType)
([] compare: 'modelType -> 'modelType -> ScalarAttributeComparison)
=
- Attributes.defineScalar<'modelType, 'valueType>
- bindableProperty.PropertyName
- convertValue
- compare
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineScalar<'modelType, 'valueType> bindableProperty.PropertyName convertValue compare (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome v -> target.SetValue(bindableProperty, v))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome v -> target.SetValue(bindableProperty, v))
/// Define an attribute for a BindableProperty supporting equality comparison
let inline defineBindableWithEquality<'T when 'T: equality> (bindableProperty: BindableProperty) =
- Attributes.defineSimpleScalarWithEquality<'T>
- bindableProperty.PropertyName
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSimpleScalarWithEquality<'T> bindableProperty.PropertyName (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome v -> target.SetValue(bindableProperty, v))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome v -> target.SetValue(bindableProperty, v))
/// Define an attribute that can fit into 8 bytes encoded as uint64 (such as float or bool) for a BindableProperty
let inline defineSmallBindable<'T> (bindableProperty: BindableProperty) ([] decode: uint64 -> 'T) =
- Attributes.defineSmallScalar<'T>
- bindableProperty.PropertyName
- decode
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSmallScalar<'T> bindableProperty.PropertyName decode (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome v -> target.SetValue(bindableProperty, v))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome v -> target.SetValue(bindableProperty, v))
/// Define a float attribute for a BindableProperty and encode it as a small scalar (8 bytes)
let inline defineBindableFloat (bindableProperty: BindableProperty) =
@@ -142,67 +128,51 @@ module Attributes =
/// Note if you want to use Microsoft.Maui.Color directly you can do that with "defineBindable".
/// However, XF.Color will be boxed and thus slower.
let inline defineBindableColor (bindableProperty: BindableProperty) : SmallScalarAttributeDefinition =
- Attributes.defineSmallScalar
- bindableProperty.PropertyName
- SmallScalars.FabColor.decode
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSmallScalar bindableProperty.PropertyName SmallScalars.FabColor.decode (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome v -> target.SetValue(bindableProperty, v.ToXFColor()))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome v -> target.SetValue(bindableProperty, v.ToXFColor()))
/// Define an enum attribute for a BindableProperty and encode it as a small scalar (8 bytes)
- let inline defineBindableEnum< ^T when ^T: enum>
- (bindableProperty: BindableProperty)
- : SmallScalarAttributeDefinition< ^T > =
- Attributes.defineEnum< ^T>
- bindableProperty.PropertyName
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ let inline defineBindableEnum< ^T when ^T: enum> (bindableProperty: BindableProperty) : SmallScalarAttributeDefinition< ^T > =
+ Attributes.defineEnum< ^T> bindableProperty.PropertyName (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome v -> target.SetValue(bindableProperty, v))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome v -> target.SetValue(bindableProperty, v))
/// Define an attribute that supports values for both Light and Dark themes
let inline defineBindableAppTheme<'T when 'T: equality> (bindableProperty: BindableProperty) =
- Attributes.defineSimpleScalarWithEquality>
- bindableProperty.PropertyName
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSimpleScalarWithEquality> bindableProperty.PropertyName (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome { Light = light; Dark = dark } when dark = light -> target.SetValue(bindableProperty, light)
- | ValueSome { Light = light; Dark = dark } -> target.SetAppTheme(bindableProperty, light, dark))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
+ | ValueSome { Light = light; Dark = dark } when dark = light -> target.SetValue(bindableProperty, light)
+ | ValueSome { Light = light; Dark = dark } -> target.SetAppTheme(bindableProperty, light, dark))
/// Define a Color attribute that supports values for both Light and Dark themes
/// Note that we use FabColor here because we can encode two into 8 bytes
/// Thus we can avoid heap allocations
let inline defineBindableAppThemeColor (bindableProperty: BindableProperty) =
- Attributes.defineSmallScalar>
- bindableProperty.PropertyName
- SmallScalars.ThemedColor.decode
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSmallScalar> bindableProperty.PropertyName SmallScalars.ThemedColor.decode (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(bindableProperty)
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(bindableProperty)
- | ValueSome { Light = light; Dark = dark } when light = dark ->
- target.SetValue(bindableProperty, light.ToXFColor())
+ | ValueSome { Light = light; Dark = dark } when light = dark -> target.SetValue(bindableProperty, light.ToXFColor())
- | ValueSome { Light = light; Dark = dark } ->
- target.SetAppTheme(bindableProperty, light.ToXFColor(), dark.ToXFColor()))
+ | ValueSome { Light = light; Dark = dark } -> target.SetAppTheme(bindableProperty, light.ToXFColor(), dark.ToXFColor()))
/// Define an attribute storing a Widget for a bindable property
let inline defineBindableWidget (bindableProperty: BindableProperty) =
Attributes.definePropertyWidget
bindableProperty.PropertyName
- (fun target ->
- (target :?> BindableObject)
- .GetValue(bindableProperty))
+ (fun target -> (target :?> BindableObject).GetValue(bindableProperty))
(fun target value ->
let bindableObject = target :?> BindableObject
@@ -250,10 +220,9 @@ module Attributes =
// Set the new event handler
let handler =
- EventHandler<'args>
- (fun _ args ->
- let r = curr.Event args
- Dispatcher.dispatch node r)
+ EventHandler<'args>(fun _ args ->
+ let r = curr.Event args
+ Dispatcher.dispatch node r)
node.SetHandler(name, ValueSome handler)
event.AddHandler(handler))
diff --git a/src/Fabulous.MauiControls/Controls.fs b/src/Fabulous.MauiControls/Controls.fs
index e40647a..92ad81b 100644
--- a/src/Fabulous.MauiControls/Controls.fs
+++ b/src/Fabulous.MauiControls/Controls.fs
@@ -47,8 +47,7 @@ type FabulousContentPage() as this =
inherit ContentPage()
do Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(this, true)
- let sizeAllocated =
- Event, _>()
+ let sizeAllocated = Event, _>()
[]
member __.SizeAllocated = sizeAllocated.Publish
@@ -70,8 +69,7 @@ type PositionChangedEventArgs(previousPosition: int, currentPosition: int) =
type FabulousTimePicker() =
inherit TimePicker()
- let timeSelected =
- Event, _>()
+ let timeSelected = Event, _>()
[]
member _.TimeSelected = timeSelected.Publish
@@ -92,8 +90,7 @@ type CustomEntryCell() =
let mutable oldText = ""
- let textChanged =
- Event, _>()
+ let textChanged = Event, _>()
[]
member _.TextChanged = textChanged.Publish
@@ -116,8 +113,7 @@ type CustomPicker() =
let mutable oldSelectedIndex = -1
- let selectedIndexChanged =
- Event, _>()
+ let selectedIndexChanged = Event, _>()
[]
member _.CustomSelectedIndexChanged = selectedIndexChanged.Publish
diff --git a/src/Fabulous.MauiControls/FabColor.fs b/src/Fabulous.MauiControls/FabColor.fs
index 1b0d781..09a3df0 100644
--- a/src/Fabulous.MauiControls/FabColor.fs
+++ b/src/Fabulous.MauiControls/FabColor.fs
@@ -3,14 +3,15 @@ namespace Fabulous.Maui
open System.Runtime.CompilerServices
open Microsoft.Maui.Graphics
-/// Fabulous specific representation of a Color for XamarinForms.
+/// Fabulous specific representation of a Color for Maui.Controls.
/// Note that it is limited to 4 bytes (vs other representations that might use float for a channel)
/// to optimize allocations and CPU cache misses in diffing algorithm.
/// For all practical use-cases 4 bytes is likely to be more than enough to represent a color,
-/// however you can use XamarinForms.Color directly if you need absolute color precision with custom declared attributes.
+/// however you can use Maui.Controls.Color directly if you need absolute color precision with custom declared attributes.
[]
type FabColor =
{ RGBA: uint32 }
+
member inline x.R: uint8 = (x.RGBA &&& 0xFF000000u) >>> 24 |> uint8
member inline x.G: uint8 = (x.RGBA &&& 0x00FF0000u) >>> 16 |> uint8
@@ -37,14 +38,10 @@ type ColorConversion() =
module FabColor =
/// Converts hex string into FabColor.
- /// It uses XamarinForms.Color to parse, thus it expects the same format.
+ /// It uses Maui.Controls.Color to parse, thus it expects the same format.
/// Expected format: "AARRGGBB","RRGGBB", "ARGB" or "RGB"
let inline fromHex (hex: string) : FabColor = Color.FromArgb(hex).ToFabColor()
/// Creates a FabColor from 4 byte size components. Expects RGBA ordering.
let inline fromRGBA (r: uint8) (g: uint8) (b: uint8) (a: uint8) : FabColor =
- { RGBA =
- ((uint32 r <<< 24)
- ||| (uint32 g <<< 16)
- ||| (uint32 b <<< 8)
- ||| uint32 a) }
+ { RGBA = ((uint32 r <<< 24) ||| (uint32 g <<< 16) ||| (uint32 b <<< 8) ||| uint32 a) }
diff --git a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
index c46e38f..77e9273 100644
--- a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
+++ b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
@@ -1,147 +1,156 @@
-
-
- net7.0;net7.0-ios;net7.0-android;net7.0-maccatalyst
- true
- true
- true
- PackageREADME.md
- nuget-icon.png
- Apache-2.0
- 2.1.0
- https://github.com/fabulous-dev/Fabulous.MauiControls
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net7.0;net7.0-ios;net7.0-android;net7.0-maccatalyst
+ true
+ true
+
+
+
+ Declarative UIs for .NET MAUI with F# and MVU, using Fabulous
+ true
+ en-US
+
+
+
+ true
+ true
+ true
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Fabulous.MauiControls/PackageREADME.md b/src/Fabulous.MauiControls/PackageREADME.md
deleted file mode 100644
index fc6d532..0000000
--- a/src/Fabulous.MauiControls/PackageREADME.md
+++ /dev/null
@@ -1 +0,0 @@
-Microsoft.Maui.Controls implementation for Fabulous
\ No newline at end of file
diff --git a/src/Fabulous.MauiControls/Program.fs b/src/Fabulous.MauiControls/Program.fs
index 0e48f9d..0fbee0b 100644
--- a/src/Fabulous.MauiControls/Program.fs
+++ b/src/Fabulous.MauiControls/Program.fs
@@ -14,7 +14,7 @@ module ViewHelpers =
match widget.ScalarAttributes with
| ValueNone -> ValueNone
| ValueSome scalarAttrs ->
- match Array.tryFind(fun (attr: ScalarAttribute) -> attr.Key = def.Key) scalarAttrs with
+ match Array.tryFind (fun (attr: ScalarAttribute) -> attr.Key = def.Key) scalarAttrs with
| None -> ValueNone
| Some attr -> ValueSome(unbox<'data> attr.Value)
@@ -22,14 +22,13 @@ module ViewHelpers =
match widget.WidgetCollectionAttributes with
| ValueNone -> ValueNone
| ValueSome collectionAttrs ->
- match Array.tryFind(fun (attr: WidgetCollectionAttribute) -> attr.Key = def.Key) collectionAttrs with
+ match Array.tryFind (fun (attr: WidgetCollectionAttribute) -> attr.Key = def.Key) collectionAttrs with
| None -> ValueNone
| Some attr -> ValueSome attr.Value
/// Extend the canReuseView function to check Microsoft.Maui specific constraints
let rec canReuseView (prev: Widget) (curr: Widget) =
- if ViewHelpers.canReuseView prev curr
- && canReuseAutomationId prev curr then
+ if ViewHelpers.canReuseView prev curr && canReuseAutomationId prev curr then
let def = WidgetDefinitionStore.get curr.Key
// TargetType can be null for MemoWidget
@@ -47,11 +46,9 @@ module ViewHelpers =
/// Check whether widgets have compatible automation ids.
/// Microsoft.Maui only allows setting the automation id once so we can't reuse a control if the id is not the same.
and private canReuseAutomationId (prev: Widget) (curr: Widget) =
- let prevIdOpt =
- tryGetScalarValue prev Element.AutomationId
+ let prevIdOpt = tryGetScalarValue prev Element.AutomationId
- let currIdOpt =
- tryGetScalarValue curr Element.AutomationId
+ let currIdOpt = tryGetScalarValue curr Element.AutomationId
match prevIdOpt with
| ValueSome _ when prevIdOpt <> currIdOpt -> false
@@ -62,11 +59,9 @@ module ViewHelpers =
// NavigationPage can be reused only if the pages don't change their type (added/removed pages don't prevent reuse)
// E.g. If the first page switch from ContentPage to TabbedPage, the NavigationPage can't be reused.
and private canReuseNavigationPage (prev: Widget) (curr: Widget) =
- let prevPages =
- tryGetWidgetCollectionValue prev NavigationPage.Pages
+ let prevPages = tryGetWidgetCollectionValue prev NavigationPage.Pages
- let currPages =
- tryGetWidgetCollectionValue curr NavigationPage.Pages
+ let currPages = tryGetWidgetCollectionValue curr NavigationPage.Pages
match struct (prevPages, currPages) with
| ValueSome prevPages, ValueSome currPages ->
@@ -100,11 +95,7 @@ module ViewHelpers =
false
module Program =
- let inline private define
- (init: 'arg -> 'model * Cmd<'msg>)
- (update: 'msg -> 'model -> 'model * Cmd<'msg>)
- (view: 'model -> WidgetBuilder<'msg, 'marker>)
- =
+ let inline private define (init: 'arg -> 'model * Cmd<'msg>) (update: 'msg -> 'model -> 'model * Cmd<'msg>) (view: 'model -> WidgetBuilder<'msg, 'marker>) =
{ Init = init
Update = (fun (msg, model) -> update msg model)
Subscribe = fun _ -> Cmd.none
@@ -116,15 +107,11 @@ module Program =
/// Create a program for a static view
let stateless (view: unit -> WidgetBuilder) =
- define(fun () -> (), Cmd.none) (fun () () -> (), Cmd.none) view
+ define (fun () -> (), Cmd.none) (fun () () -> (), Cmd.none) view
/// Create a program using an MVU loop
- let stateful
- (init: 'arg -> 'model)
- (update: 'msg -> 'model -> 'model)
- (view: 'model -> WidgetBuilder<'msg, 'marker>)
- =
- define(fun arg -> init arg, Cmd.none) (fun msg model -> update msg model, Cmd.none) view
+ let stateful (init: 'arg -> 'model) (update: 'msg -> 'model -> 'model) (view: 'model -> WidgetBuilder<'msg, 'marker>) =
+ define (fun arg -> init arg, Cmd.none) (fun msg model -> update msg model, Cmd.none) view
/// Create a program using an MVU loop. Add support for Cmd
let statefulWithCmd
@@ -143,33 +130,24 @@ module Program =
=
let mapCmds cmdMsgs = cmdMsgs |> List.map mapCmd |> Cmd.batch
- define
- (fun arg -> let m, c = init arg in m, mapCmds c)
- (fun msg model -> let m, c = update msg model in m, mapCmds c)
- view
+ define (fun arg -> let m, c = init arg in m, mapCmds c) (fun msg model -> let m, c = update msg model in m, mapCmds c) view
/// Start the program
- let startApplicationWithArgs
- (arg: 'arg)
- (program: Program<'arg, 'model, 'msg, #Fabulous.Maui.IApplication>)
- : Microsoft.Maui.Controls.Application =
+ let startApplicationWithArgs (arg: 'arg) (program: Program<'arg, 'model, 'msg, #Fabulous.Maui.IApplication>) : Microsoft.Maui.Controls.Application =
let runner = Runners.create program
runner.Start(arg)
let adapter = ViewAdapters.create ViewNode.get runner
adapter.CreateView() |> unbox
/// Start the program
- let startApplication
- (program: Program)
- : Microsoft.Maui.Controls.Application =
- startApplicationWithArgs() program
+ let startApplication (program: Program) : Microsoft.Maui.Controls.Application =
+ startApplicationWithArgs () program
/// Subscribe to external source of events.
/// The subscription is called once - with the initial model, but can dispatch new messages at any time.
let withSubscription (subscribe: 'model -> Cmd<'msg>) (program: Program<'arg, 'model, 'msg, 'marker>) =
let sub model =
- Cmd.batch [ program.Subscribe model
- subscribe model ]
+ Cmd.batch [ program.Subscribe model; subscribe model ]
{ program with Subscribe = sub }
@@ -183,8 +161,7 @@ module Program =
let initModel, cmd = program.Init(arg)
trace("Initial model: {0}", $"%0A{initModel}")
initModel, cmd
- with
- | e ->
+ with e ->
trace("Error in init function: {0}", $"%0A{e}")
reraise()
@@ -195,8 +172,7 @@ module Program =
let newModel, cmd = program.Update(msg, model)
trace("Updated model: {0}", $"%0A{newModel}")
newModel, cmd
- with
- | e ->
+ with e ->
trace("Error in model function: {0}", $"%0A{e}")
reraise()
@@ -207,24 +183,20 @@ module Program =
let info = program.View(model)
trace("View result: {0}", $"%0A{info}")
info
- with
- | e ->
+ with e ->
trace("Error in view function: {0}", $"%0A{e}")
reraise()
{ program with
- Init = traceInit
- Update = traceUpdate
- View = traceView }
+ Init = traceInit
+ Update = traceUpdate
+ View = traceView }
/// Configure how the unhandled exceptions happening during the execution of a Fabulous app with be handled
let withExceptionHandler (handler: exn -> bool) (program: Program<'arg, 'model, 'msg, 'marker>) =
- { program with
- ExceptionHandler = handler }
+ { program with ExceptionHandler = handler }
[]
module CmdMsg =
let batch mapCmdMsgFn mapCmdFn cmdMsgs =
- cmdMsgs
- |> List.map(mapCmdMsgFn >> Cmd.map mapCmdFn)
- |> Cmd.batch
+ cmdMsgs |> List.map(mapCmdMsgFn >> Cmd.map mapCmdFn) |> Cmd.batch
diff --git a/src/Fabulous.MauiControls/README.md b/src/Fabulous.MauiControls/README.md
new file mode 100644
index 0000000..22be1aa
--- /dev/null
+++ b/src/Fabulous.MauiControls/README.md
@@ -0,0 +1,19 @@
+# Fabulous for .NET MAUI (Microsoft.Maui.Controls)
+
+Fabulous.MauiControls brings the great development experience of Fabulous to .NET MAUI, allowing you to take advantage of the latest cross-platform UI framework from Microsoft with a tailored declarative UI DSL and clean architecture.
+
+Deploy to any platform supported by .NET MAUI, such as Android, iOS, macOS, Windows, Linux and more!
+
+### Getting Started
+
+You can start your new Fabulous.MauiControls app in a matter of seconds using the dotnet CLI templates.
+For a starter guide see our [documentation](https://docs.fabulous.dev/v2/maui.controls/getting-started).
+
+```sh
+dotnet new install Fabulous.MauiControls.Templates
+dotnet new fabulous-mauicontrols -n MyApp
+```
+
+### Documentation
+
+Documentation can be found at https://docs.fabulous.dev/v2/maui.controls
\ No newline at end of file
diff --git a/src/Fabulous.MauiControls/ViewNode.fs b/src/Fabulous.MauiControls/ViewNode.fs
index 826e33b..591cd1c 100644
--- a/src/Fabulous.MauiControls/ViewNode.fs
+++ b/src/Fabulous.MauiControls/ViewNode.fs
@@ -8,10 +8,7 @@ module ViewNode =
BindableProperty.Create("ViewNode", typeof, typeof, null)
let get (target: obj) =
- (target :?> BindableObject)
- .GetValue(ViewNodeProperty)
- :?> IViewNode
+ (target :?> BindableObject).GetValue(ViewNodeProperty) :?> IViewNode
let set (node: IViewNode) (target: obj) =
- (target :?> BindableObject)
- .SetValue(ViewNodeProperty, node)
+ (target :?> BindableObject).SetValue(ViewNodeProperty, node)
diff --git a/src/Fabulous.MauiControls/Views/Any.fs b/src/Fabulous.MauiControls/Views/Any.fs
index 75512eb..338eede 100644
--- a/src/Fabulous.MauiControls/Views/Any.fs
+++ b/src/Fabulous.MauiControls/Views/Any.fs
@@ -6,6 +6,7 @@ open Fabulous.Maui
[]
module AnyBuilders =
type Fabulous.Maui.View with
+
/// Downcast to IView to allow to return different types of views in a single expression (e.g. if/else, match with pattern, etc.)
static member AnyView<'msg, 'marker when 'marker :> Fabulous.Maui.IView>(widget: WidgetBuilder<'msg, 'marker>) =
WidgetBuilder<'msg, IView>(widget.Key, widget.Attributes)
diff --git a/src/Fabulous.MauiControls/Views/Application.fs b/src/Fabulous.MauiControls/Views/Application.fs
index 2965e14..aab4a04 100644
--- a/src/Fabulous.MauiControls/Views/Application.fs
+++ b/src/Fabulous.MauiControls/Views/Application.fs
@@ -19,55 +19,41 @@ module Application =
(fun target value -> (target :?> Application).MainPage <- value)
let Resources =
- Attributes.defineSimpleScalarWithEquality
- "Application_Resources"
- (fun _ newValueOpt node ->
- let application = node.Target :?> Application
+ Attributes.defineSimpleScalarWithEquality "Application_Resources" (fun _ newValueOpt node ->
+ let application = node.Target :?> Application
- let value =
- match newValueOpt with
- | ValueNone -> application.Resources
- | ValueSome v -> v
+ let value =
+ match newValueOpt with
+ | ValueNone -> application.Resources
+ | ValueSome v -> v
- application.Resources <- value)
+ application.Resources <- value)
let UserAppTheme =
- Attributes.defineEnum
- "Application_UserAppTheme"
- (fun _ newValueOpt node ->
- let application = node.Target :?> Application
+ Attributes.defineEnum "Application_UserAppTheme" (fun _ newValueOpt node ->
+ let application = node.Target :?> Application
- let value =
- match newValueOpt with
- | ValueNone -> AppTheme.Unspecified
- | ValueSome v -> v
+ let value =
+ match newValueOpt with
+ | ValueNone -> AppTheme.Unspecified
+ | ValueSome v -> v
- application.UserAppTheme <- value)
+ application.UserAppTheme <- value)
let RequestedThemeChanged =
- Attributes.defineEvent
- "Application_RequestedThemeChanged"
- (fun target -> (target :?> Application).RequestedThemeChanged)
+ Attributes.defineEvent "Application_RequestedThemeChanged" (fun target -> (target :?> Application).RequestedThemeChanged)
let ModalPopped =
- Attributes.defineEvent
- "Application_ModalPopped"
- (fun target -> (target :?> Application).ModalPopped)
+ Attributes.defineEvent "Application_ModalPopped" (fun target -> (target :?> Application).ModalPopped)
let ModalPopping =
- Attributes.defineEvent
- "Application_ModalPopping"
- (fun target -> (target :?> Application).ModalPopping)
+ Attributes.defineEvent "Application_ModalPopping" (fun target -> (target :?> Application).ModalPopping)
let ModalPushed =
- Attributes.defineEvent
- "Application_ModalPushed"
- (fun target -> (target :?> Application).ModalPushed)
+ Attributes.defineEvent "Application_ModalPushed" (fun target -> (target :?> Application).ModalPushed)
let ModalPushing =
- Attributes.defineEvent
- "Application_ModalPushing"
- (fun target -> (target :?> Application).ModalPushing)
+ Attributes.defineEvent "Application_ModalPushing" (fun target -> (target :?> Application).ModalPushing)
let Start =
Attributes.defineEventNoArg "Application_Start" (fun target -> (target :?> CustomApplication).Start)
@@ -81,10 +67,9 @@ module Application =
[]
module ApplicationBuilders =
type Fabulous.Maui.View with
+
static member inline Application<'msg, 'marker when 'marker :> IPage>(mainPage: WidgetBuilder<'msg, 'marker>) =
- WidgetHelpers.buildWidgets<'msg, IApplication>
- Application.WidgetKey
- [| Application.MainPage.WithValue(mainPage.Compile()) |]
+ WidgetHelpers.buildWidgets<'msg, IApplication> Application.WidgetKey [| Application.MainPage.WithValue(mainPage.Compile()) |]
[]
type ApplicationModifiers =
@@ -97,45 +82,23 @@ type ApplicationModifiers =
this.AddScalar(Application.Resources.WithValue(value))
[]
- static member inline onRequestedThemeChanged
- (
- this: WidgetBuilder<'msg, #IApplication>,
- onRequestedThemeChanged: AppTheme -> 'msg
- ) =
- this.AddScalar(
- Application.RequestedThemeChanged.WithValue(fun args -> onRequestedThemeChanged args.RequestedTheme |> box)
- )
+ static member inline onRequestedThemeChanged(this: WidgetBuilder<'msg, #IApplication>, onRequestedThemeChanged: AppTheme -> 'msg) =
+ this.AddScalar(Application.RequestedThemeChanged.WithValue(fun args -> onRequestedThemeChanged args.RequestedTheme |> box))
[]
- static member inline onModalPopped
- (
- this: WidgetBuilder<'msg, #IApplication>,
- onModalPopped: ModalPoppedEventArgs -> 'msg
- ) =
+ static member inline onModalPopped(this: WidgetBuilder<'msg, #IApplication>, onModalPopped: ModalPoppedEventArgs -> 'msg) =
this.AddScalar(Application.ModalPopped.WithValue(onModalPopped >> box))
[]
- static member inline onModalPopping
- (
- this: WidgetBuilder<'msg, #IApplication>,
- onModalPopping: ModalPoppingEventArgs -> 'msg
- ) =
+ static member inline onModalPopping(this: WidgetBuilder<'msg, #IApplication>, onModalPopping: ModalPoppingEventArgs -> 'msg) =
this.AddScalar(Application.ModalPopping.WithValue(onModalPopping >> box))
[]
- static member inline onModalPushed
- (
- this: WidgetBuilder<'msg, #IApplication>,
- onModalPushed: ModalPushedEventArgs -> 'msg
- ) =
+ static member inline onModalPushed(this: WidgetBuilder<'msg, #IApplication>, onModalPushed: ModalPushedEventArgs -> 'msg) =
this.AddScalar(Application.ModalPushed.WithValue(onModalPushed >> box))
[]
- static member inline onModalPushing
- (
- this: WidgetBuilder<'msg, #IApplication>,
- onModalPushing: ModalPushingEventArgs -> 'msg
- ) =
+ static member inline onModalPushing(this: WidgetBuilder<'msg, #IApplication>, onModalPushing: ModalPushingEventArgs -> 'msg) =
this.AddScalar(Application.ModalPushing.WithValue(onModalPushing >> box))
/// Dispatch a message when the application starts
diff --git a/src/Fabulous.MauiControls/Views/Brushes/LinearGradientBrush.fs b/src/Fabulous.MauiControls/Views/Brushes/LinearGradientBrush.fs
index d36360d..6a42046 100644
--- a/src/Fabulous.MauiControls/Views/Brushes/LinearGradientBrush.fs
+++ b/src/Fabulous.MauiControls/Views/Brushes/LinearGradientBrush.fs
@@ -21,6 +21,7 @@ module LinearGradientBrush =
[]
module LinearGradientBrushBuilders =
type Fabulous.Maui.View with
+
/// LinearGradientBrush paints an area with a linear gradient, which blends two or more colors along a line known as the gradient axis.
/// EndPoint, of type Point, which represents the ending two-dimensional coordinates of the linear gradient. The default value of this property is (1,1).
/// StartPoint, of type Point, which represents the starting two-dimensional coordinates of the linear gradient. The default value of this property is (0,0).
diff --git a/src/Fabulous.MauiControls/Views/Brushes/RadialGradientBrush.fs b/src/Fabulous.MauiControls/Views/Brushes/RadialGradientBrush.fs
index 1e0c9ef..6e9329c 100644
--- a/src/Fabulous.MauiControls/Views/Brushes/RadialGradientBrush.fs
+++ b/src/Fabulous.MauiControls/Views/Brushes/RadialGradientBrush.fs
@@ -15,12 +15,12 @@ module RadialGradientBrush =
let Center =
Attributes.defineBindableWithEquality RadialGradientBrush.CenterProperty
- let Radius =
- Attributes.defineBindableFloat RadialGradientBrush.RadiusProperty
+ let Radius = Attributes.defineBindableFloat RadialGradientBrush.RadiusProperty
[]
module RadialGradientBrushBuilders =
type Fabulous.Maui.View with
+
/// RadialGradientBrush paints an area with a radial gradient, which blends two or more colors across a circle.
/// Center, of type Point, which represents the center point of the circle for the radial gradient. The default value of this property is (0.5,0.5).
/// Radius, of type float, which represents the radius of the circle for the radial gradient. The default value of this property is 0.5.
diff --git a/src/Fabulous.MauiControls/Views/Brushes/SolidColorBrush.fs b/src/Fabulous.MauiControls/Views/Brushes/SolidColorBrush.fs
index b22ca9c..b17ef9d 100644
--- a/src/Fabulous.MauiControls/Views/Brushes/SolidColorBrush.fs
+++ b/src/Fabulous.MauiControls/Views/Brushes/SolidColorBrush.fs
@@ -11,17 +11,14 @@ module SolidColorBrush =
let WidgetKey = Widgets.register()
- let Color =
- Attributes.defineBindableAppThemeColor SolidColorBrush.ColorProperty
+ let Color = Attributes.defineBindableAppThemeColor SolidColorBrush.ColorProperty
[]
module SolidColorBrushBuilders =
type Fabulous.Maui.View with
+
/// SolidColorBrush, which paints an area with a solid color. For more information, see Solid color brushes.
/// The color in light theme.
/// The color in dark theme.
static member inline SolidColorBrush(light: FabColor, ?dark: FabColor) =
- WidgetBuilder<'msg, ISolidColorBrush>(
- SolidColorBrush.WidgetKey,
- SolidColorBrush.Color.WithValue(AppTheme.create light dark)
- )
+ WidgetBuilder<'msg, ISolidColorBrush>(SolidColorBrush.WidgetKey, SolidColorBrush.Color.WithValue(AppTheme.create light dark))
diff --git a/src/Fabulous.MauiControls/Views/Brushes/_GradientBrush.fs b/src/Fabulous.MauiControls/Views/Brushes/_GradientBrush.fs
index 21b1aa6..bf63bb4 100644
--- a/src/Fabulous.MauiControls/Views/Brushes/_GradientBrush.fs
+++ b/src/Fabulous.MauiControls/Views/Brushes/_GradientBrush.fs
@@ -11,6 +11,4 @@ type IGradientBrush =
module GradientBrush =
let Children =
- Attributes.defineListWidgetCollection
- "GradientBrush_GradientStops"
- (fun target -> (target :?> GradientBrush).GradientStops :> IList<_>)
+ Attributes.defineListWidgetCollection "GradientBrush_GradientStops" (fun target -> (target :?> GradientBrush).GradientStops :> IList<_>)
diff --git a/src/Fabulous.MauiControls/Views/Brushes/_GradientStop.fs b/src/Fabulous.MauiControls/Views/Brushes/_GradientStop.fs
index c5c0590..1284cbe 100644
--- a/src/Fabulous.MauiControls/Views/Brushes/_GradientStop.fs
+++ b/src/Fabulous.MauiControls/Views/Brushes/_GradientStop.fs
@@ -12,15 +12,14 @@ module GradientStop =
let WidgetKey = Widgets.register()
- let Color =
- Attributes.defineBindableAppThemeColor GradientStop.ColorProperty
+ let Color = Attributes.defineBindableAppThemeColor GradientStop.ColorProperty
- let Offset =
- Attributes.defineBindableFloat GradientStop.OffsetProperty
+ let Offset = Attributes.defineBindableFloat GradientStop.OffsetProperty
[]
module GradientStopBuilders =
type Fabulous.Maui.View with
+
/// GradientStop objects to the LinearGradientBrush.GradientStops collection, that specify the colors in the gradient and their positions.
/// The color in light theme.
/// The color in dark theme.
diff --git a/src/Fabulous.MauiControls/Views/Cells/EntryCell.fs b/src/Fabulous.MauiControls/Views/Cells/EntryCell.fs
index e328f0a..d24871e 100644
--- a/src/Fabulous.MauiControls/Views/Cells/EntryCell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/EntryCell.fs
@@ -11,11 +11,9 @@ type IEntryCell =
module EntryCell =
let WidgetKey = Widgets.register()
- let Label =
- Attributes.defineBindableWithEquality EntryCell.LabelProperty
+ let Label = Attributes.defineBindableWithEquality EntryCell.LabelProperty
- let LabelColor =
- Attributes.defineBindableAppThemeColor EntryCell.LabelColorProperty
+ let LabelColor = Attributes.defineBindableAppThemeColor EntryCell.LabelColorProperty
let Placeholder =
Attributes.defineBindableWithEquality EntryCell.PlaceholderProperty
@@ -30,10 +28,7 @@ module EntryCell =
Attributes.defineBindableWithEquality EntryCell.KeyboardProperty
let TextWithEvent =
- Attributes.defineBindableWithEvent
- "EntryCell_TextChanged"
- EntryCell.TextProperty
- (fun target -> (target :?> CustomEntryCell).TextChanged)
+ Attributes.defineBindableWithEvent "EntryCell_TextChanged" EntryCell.TextProperty (fun target -> (target :?> CustomEntryCell).TextChanged)
let OnCompleted =
Attributes.defineEventNoArg "EntryCell_Completed" (fun target -> (target :?> EntryCell).Completed)
@@ -42,13 +37,12 @@ module EntryCell =
module EntryCellBuilders =
type Fabulous.Maui.View with
+
static member inline EntryCell<'msg>(label: string, text: string, onTextChanged: string -> 'msg) =
WidgetBuilder<'msg, IEntryCell>(
EntryCell.WidgetKey,
EntryCell.Label.WithValue(label),
- EntryCell.TextWithEvent.WithValue(
- ValueEventData.create text (fun args -> onTextChanged args.NewTextValue |> box)
- )
+ EntryCell.TextWithEvent.WithValue(ValueEventData.create text (fun args -> onTextChanged args.NewTextValue |> box))
)
[]
diff --git a/src/Fabulous.MauiControls/Views/Cells/ImageCell.fs b/src/Fabulous.MauiControls/Views/Cells/ImageCell.fs
index 69f4ec1..c54bab5 100644
--- a/src/Fabulous.MauiControls/Views/Cells/ImageCell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/ImageCell.fs
@@ -18,12 +18,9 @@ module ImageCell =
[]
module ImageCellBuilders =
type Fabulous.Maui.View with
+
static member inline ImageCell<'msg>(text: string, light: ImageSource, ?dark: ImageSource) =
- WidgetBuilder<'msg, IImageCell>(
- ImageCell.WidgetKey,
- TextCell.Text.WithValue(text),
- ImageCell.ImageSource.WithValue(AppTheme.create light dark)
- )
+ WidgetBuilder<'msg, IImageCell>(ImageCell.WidgetKey, TextCell.Text.WithValue(text), ImageCell.ImageSource.WithValue(AppTheme.create light dark))
static member inline ImageCell<'msg>(text: string, light: string, ?dark: string) =
let light = ImageSource.FromFile(light)
diff --git a/src/Fabulous.MauiControls/Views/Cells/SwitchCell.fs b/src/Fabulous.MauiControls/Views/Cells/SwitchCell.fs
index 0ae4cef..08db798 100644
--- a/src/Fabulous.MauiControls/Views/Cells/SwitchCell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/SwitchCell.fs
@@ -10,21 +10,17 @@ type ISwitchCell =
module SwitchCell =
let WidgetKey = Widgets.register()
- let Text =
- Attributes.defineBindableWithEquality SwitchCell.TextProperty
+ let Text = Attributes.defineBindableWithEquality SwitchCell.TextProperty
let OnWithEvent =
- Attributes.defineBindableWithEvent
- "SwitchCell_OnChanged"
- SwitchCell.OnProperty
- (fun target -> (target :?> SwitchCell).OnChanged)
+ Attributes.defineBindableWithEvent "SwitchCell_OnChanged" SwitchCell.OnProperty (fun target -> (target :?> SwitchCell).OnChanged)
- let OnColor =
- Attributes.defineBindableAppThemeColor SwitchCell.OnColorProperty
+ let OnColor = Attributes.defineBindableAppThemeColor SwitchCell.OnColorProperty
[]
module SwitchCellBuilders =
type Fabulous.Maui.View with
+
static member inline SwitchCell<'msg>(text: string, value: bool, onChanged: bool -> 'msg) =
WidgetBuilder<'msg, ISwitchCell>(
SwitchCell.WidgetKey,
diff --git a/src/Fabulous.MauiControls/Views/Cells/TextCell.fs b/src/Fabulous.MauiControls/Views/Cells/TextCell.fs
index d6bc6ec..c2ba903 100644
--- a/src/Fabulous.MauiControls/Views/Cells/TextCell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/TextCell.fs
@@ -10,14 +10,11 @@ type ITextCell =
module TextCell =
let WidgetKey = Widgets.register()
- let Text =
- Attributes.defineBindableWithEquality TextCell.TextProperty
+ let Text = Attributes.defineBindableWithEquality TextCell.TextProperty
- let TextColor =
- Attributes.defineBindableAppThemeColor TextCell.TextColorProperty
+ let TextColor = Attributes.defineBindableAppThemeColor TextCell.TextColorProperty
- let Detail =
- Attributes.defineBindableWithEquality TextCell.DetailProperty
+ let Detail = Attributes.defineBindableWithEquality TextCell.DetailProperty
let DetailColor =
Attributes.defineBindableAppThemeColor TextCell.DetailColorProperty
@@ -25,6 +22,7 @@ module TextCell =
[]
module TextCellBuilders =
type Fabulous.Maui.View with
+
static member inline TextCell<'msg>(text: string) =
WidgetBuilder<'msg, ITextCell>(TextCell.WidgetKey, TextCell.Text.WithValue(text))
diff --git a/src/Fabulous.MauiControls/Views/Cells/ViewCell.fs b/src/Fabulous.MauiControls/Views/Cells/ViewCell.fs
index 2e879d4..0987337 100644
--- a/src/Fabulous.MauiControls/Views/Cells/ViewCell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/ViewCell.fs
@@ -19,9 +19,8 @@ module ViewCell =
[]
module ViewCellBuilders =
type Fabulous.Maui.View with
- static member inline ViewCell<'msg, 'marker when 'marker :> Fabulous.Maui.IView>
- (view: WidgetBuilder<'msg, 'marker>)
- =
+
+ static member inline ViewCell<'msg, 'marker when 'marker :> Fabulous.Maui.IView>(view: WidgetBuilder<'msg, 'marker>) =
WidgetHelpers.buildWidgets<'msg, IViewCell> ViewCell.WidgetKey [| ViewCell.View.WithValue(view.Compile()) |]
[]
diff --git a/src/Fabulous.MauiControls/Views/Cells/_Cell.fs b/src/Fabulous.MauiControls/Views/Cells/_Cell.fs
index c886984..5b32b92 100644
--- a/src/Fabulous.MauiControls/Views/Cells/_Cell.fs
+++ b/src/Fabulous.MauiControls/Views/Cells/_Cell.fs
@@ -9,21 +9,18 @@ type ICell =
inherit Fabulous.Maui.IElement
module Cell =
- let IsEnabled =
- Attributes.defineBindableBool Cell.IsEnabledProperty
+ let IsEnabled = Attributes.defineBindableBool Cell.IsEnabledProperty
let Height =
- Attributes.defineFloat
- "Cell_Height"
- (fun _ newValueOpt node ->
- let cell = node.Target :?> Cell
+ Attributes.defineFloat "Cell_Height" (fun _ newValueOpt node ->
+ let cell = node.Target :?> Cell
- let value =
- match newValueOpt with
- | ValueNone -> cell.Height
- | ValueSome v -> v
+ let value =
+ match newValueOpt with
+ | ValueNone -> cell.Height
+ | ValueSome v -> v
- cell.Height <- value)
+ cell.Height <- value)
let Appearing =
Attributes.defineEventNoArg "Cell_Appearing" (fun target -> (target :?> Cell).Appearing)
diff --git a/src/Fabulous.MauiControls/Views/Collections/CarouselView.fs b/src/Fabulous.MauiControls/Views/Collections/CarouselView.fs
index a044469..13e69ed 100644
--- a/src/Fabulous.MauiControls/Views/Collections/CarouselView.fs
+++ b/src/Fabulous.MauiControls/Views/Collections/CarouselView.fs
@@ -15,8 +15,7 @@ module CarouselView =
let IsBounceEnabled =
Attributes.defineBindableBool CarouselView.IsBounceEnabledProperty
- let IsDragging =
- Attributes.defineBindableBool CarouselView.IsDraggingProperty
+ let IsDragging = Attributes.defineBindableBool CarouselView.IsDraggingProperty
let IsScrollAnimated =
Attributes.defineBindableBool CarouselView.IsScrollAnimatedProperty
@@ -24,74 +23,65 @@ module CarouselView =
let IsSwipeEnabled =
Attributes.defineBindableBool CarouselView.IsSwipeEnabledProperty
- let Loop =
- Attributes.defineBindableBool CarouselView.LoopProperty
+ let Loop = Attributes.defineBindableBool CarouselView.LoopProperty
let PeekAreaInsets =
Attributes.defineBindableWithEquality CarouselView.PeekAreaInsetsProperty
let IndicatorView =
- Attributes.defineSimpleScalarWithEquality>
- "CarouselView_IndicatorView"
- (fun oldValueOpt newValueOpt node ->
- let handlerOpt =
- node.TryGetHandler>(ViewRefAttributes.ViewRef.Name)
-
- // Clean up previous handler
- if handlerOpt.IsSome then
- match struct (oldValueOpt, newValueOpt) with
- | struct (ValueSome prev, _) -> prev.Attached.RemoveHandler(handlerOpt.Value)
-
- | struct (ValueNone, ValueSome curr) ->
- // Despite not having a previous value, we might still be reusing the same ViewRef
- // So we still need to clean up
- curr.Attached.RemoveHandler(handlerOpt.Value)
-
- | struct (ValueNone, ValueNone) -> ()
-
- node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueNone)
-
- let handler =
- match handlerOpt with
- | ValueSome handler -> handler
- | ValueNone ->
- let newHandler =
- EventHandler
- (fun viewRef indicatorView ->
- let carouselView = node.Target :?> CarouselView
-
- if carouselView <> null then
- carouselView.IndicatorView <- indicatorView
- else
- // CarouselView has been disposed, clean up the handler
- let handler =
- node
- .TryGetHandler>(
- ViewRefAttributes.ViewRef.Name
- )
- .Value
-
- (viewRef :?> ViewRef)
- .Attached.RemoveHandler(handler))
-
- newHandler
-
- match newValueOpt with
- | ValueNone -> node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueNone)
- | ValueSome curr ->
- curr.Attached.AddHandler(handler)
- node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueSome handler))
+ Attributes.defineSimpleScalarWithEquality> "CarouselView_IndicatorView" (fun oldValueOpt newValueOpt node ->
+ let handlerOpt =
+ node.TryGetHandler>(ViewRefAttributes.ViewRef.Name)
+
+ // Clean up previous handler
+ if handlerOpt.IsSome then
+ match struct (oldValueOpt, newValueOpt) with
+ | struct (ValueSome prev, _) -> prev.Attached.RemoveHandler(handlerOpt.Value)
+
+ | struct (ValueNone, ValueSome curr) ->
+ // Despite not having a previous value, we might still be reusing the same ViewRef
+ // So we still need to clean up
+ curr.Attached.RemoveHandler(handlerOpt.Value)
+
+ | struct (ValueNone, ValueNone) -> ()
+
+ node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueNone)
+
+ let handler =
+ match handlerOpt with
+ | ValueSome handler -> handler
+ | ValueNone ->
+ let newHandler =
+ EventHandler(fun viewRef indicatorView ->
+ let carouselView = node.Target :?> CarouselView
+
+ if carouselView <> null then
+ carouselView.IndicatorView <- indicatorView
+ else
+ // CarouselView has been disposed, clean up the handler
+ let handler =
+ node
+ .TryGetHandler>(
+ ViewRefAttributes.ViewRef.Name
+ )
+ .Value
+
+ (viewRef :?> ViewRef).Attached.RemoveHandler(handler))
+
+ newHandler
+
+ match newValueOpt with
+ | ValueNone -> node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueNone)
+ | ValueSome curr ->
+ curr.Attached.AddHandler(handler)
+ node.SetHandler(ViewRefAttributes.ViewRef.Name, ValueSome handler))
[]
module CarouselViewBuilders =
type Fabulous.Maui.View with
- static member inline CarouselView<'msg, 'itemData, 'itemMarker when 'itemMarker :> Fabulous.Maui.IView>
- (items: seq<'itemData>)
- =
- WidgetHelpers.buildItems<'msg, ICarouselView, 'itemData, 'itemMarker>
- CarouselView.WidgetKey
- ItemsView.ItemsSource
- items
+
+ static member inline CarouselView<'msg, 'itemData, 'itemMarker when 'itemMarker :> Fabulous.Maui.IView>(items: seq<'itemData>) =
+ WidgetHelpers.buildItems<'msg, ICarouselView, 'itemData, 'itemMarker> CarouselView.WidgetKey ItemsView.ItemsSource items
[]
type CarouselViewModifiers =
@@ -134,14 +124,7 @@ type CarouselViewModifiers =
CarouselViewModifiers.peekAreaInsets(this, Thickness(value))
[]
- static member inline peekAreaInsets
- (
- this: WidgetBuilder<'msg, #ICarouselView>,
- left: float,
- top: float,
- right: float,
- bottom: float
- ) =
+ static member inline peekAreaInsets(this: WidgetBuilder<'msg, #ICarouselView>, left: float, top: float, right: float, bottom: float) =
CarouselViewModifiers.peekAreaInsets(this, Thickness(left, top, right, bottom))
[]
diff --git a/src/Fabulous.MauiControls/Views/Collections/CollectionView.fs b/src/Fabulous.MauiControls/Views/Collections/CollectionView.fs
index e95ca87..c562815 100644
--- a/src/Fabulous.MauiControls/Views/Collections/CollectionView.fs
+++ b/src/Fabulous.MauiControls/Views/Collections/CollectionView.fs
@@ -29,15 +29,9 @@ module CollectionView =
| ValueSome value ->
collectionView.IsGrouped <- true
- collectionView.SetValue(
- CollectionView.ItemTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.ItemTemplate)
- )
+ collectionView.SetValue(CollectionView.ItemTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.ItemTemplate))
- collectionView.SetValue(
- CollectionView.GroupHeaderTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.HeaderTemplate)
- )
+ collectionView.SetValue(CollectionView.GroupHeaderTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.HeaderTemplate))
if value.FooterTemplate.IsSome then
collectionView.SetValue(
@@ -50,32 +44,25 @@ module CollectionView =
let SelectionMode =
Attributes.defineBindableEnum CollectionView.SelectionModeProperty
- let Header =
- Attributes.defineBindableWidget CollectionView.HeaderProperty
+ let Header = Attributes.defineBindableWidget CollectionView.HeaderProperty
- let Footer =
- Attributes.defineBindableWidget CollectionView.FooterProperty
+ let Footer = Attributes.defineBindableWidget CollectionView.FooterProperty
let ItemSizingStrategy =
Attributes.defineBindableEnum CollectionView.ItemSizingStrategyProperty
let SelectionChanged =
- Attributes.defineEvent
- "CollectionView_SelectionChanged"
- (fun target -> (target :?> CollectionView).SelectionChanged)
+ Attributes.defineEvent "CollectionView_SelectionChanged" (fun target -> (target :?> CollectionView).SelectionChanged)
[]
module CollectionViewBuilders =
type Fabulous.Maui.View with
- static member inline CollectionView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IView>
- (items: seq<'itemData>)
- =
- WidgetHelpers.buildItems<'msg, ICollectionView, 'itemData, 'itemMarker>
- CollectionView.WidgetKey
- ItemsView.ItemsSource
- items
- static member inline GroupedCollectionView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker when 'itemMarker :> IView and 'groupMarker :> IView and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
+ static member inline CollectionView<'msg, 'itemData, 'itemMarker when 'itemMarker :> IView>(items: seq<'itemData>) =
+ WidgetHelpers.buildItems<'msg, ICollectionView, 'itemData, 'itemMarker> CollectionView.WidgetKey ItemsView.ItemsSource items
+
+ static member inline GroupedCollectionView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker
+ when 'itemMarker :> IView and 'groupMarker :> IView and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
(items: seq<'groupData>)
=
WidgetHelpers.buildGroupItems<'msg, ICollectionView, 'groupData, 'itemData, 'groupMarker, 'itemMarker>
@@ -91,11 +78,7 @@ type CollectionViewModifiers =
this.AddScalar(CollectionView.SelectionMode.WithValue(value))
[]
- static member inline onSelectionChanged
- (
- this: WidgetBuilder<'msg, #ICollectionView>,
- onSelectionChanged: SelectionChangedEventArgs -> 'msg
- ) =
+ static member inline onSelectionChanged(this: WidgetBuilder<'msg, #ICollectionView>, onSelectionChanged: SelectionChangedEventArgs -> 'msg) =
this.AddScalar(CollectionView.SelectionChanged.WithValue(fun args -> onSelectionChanged args |> box))
[]
diff --git a/src/Fabulous.MauiControls/Views/Collections/ListView.fs b/src/Fabulous.MauiControls/Views/Collections/ListView.fs
index 95b653c..6212095 100644
--- a/src/Fabulous.MauiControls/Views/Collections/ListView.fs
+++ b/src/Fabulous.MauiControls/Views/Collections/ListView.fs
@@ -32,24 +32,15 @@ module ListView =
listView.SetValue(ListView.ItemsSourceProperty, value.OriginalItems)
- listView.SetValue(
- ListView.ItemTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.ItemTemplate)
- )
+ listView.SetValue(ListView.ItemTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.ItemTemplate))
- listView.SetValue(
- ListView.GroupHeaderTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.HeaderTemplate)
- ))
+ listView.SetValue(ListView.GroupHeaderTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.HeaderTemplate)))
- let Header =
- Attributes.defineBindableWidget ListView.HeaderProperty
+ let Header = Attributes.defineBindableWidget ListView.HeaderProperty
- let Footer =
- Attributes.defineBindableWidget ListView.FooterProperty
+ let Footer = Attributes.defineBindableWidget ListView.FooterProperty
- let RowHeight =
- Attributes.defineBindableInt ListView.RowHeightProperty
+ let RowHeight = Attributes.defineBindableInt ListView.RowHeightProperty
let SelectionMode =
Attributes.defineBindableEnum ListView.SelectionModeProperty
@@ -57,11 +48,9 @@ module ListView =
let IsPullToRefreshEnabled =
Attributes.defineBindableBool ListView.IsPullToRefreshEnabledProperty
- let IsRefreshing =
- Attributes.defineBindableBool ListView.IsRefreshingProperty
+ let IsRefreshing = Attributes.defineBindableBool ListView.IsRefreshingProperty
- let HasUnevenRows =
- Attributes.defineBindableBool ListView.HasUnevenRowsProperty
+ let HasUnevenRows = Attributes.defineBindableBool ListView.HasUnevenRowsProperty
let SeparatorVisibility =
Attributes.defineBindableEnum ListView.SeparatorVisibilityProperty
@@ -79,24 +68,16 @@ module ListView =
Attributes.defineBindableEnum ListView.VerticalScrollBarVisibilityProperty
let ItemAppearing =
- Attributes.defineEvent
- "ListView_ItemAppearing"
- (fun target -> (target :?> ListView).ItemAppearing)
+ Attributes.defineEvent "ListView_ItemAppearing" (fun target -> (target :?> ListView).ItemAppearing)
let ItemDisappearing =
- Attributes.defineEvent
- "ListView_ItemDisappearing"
- (fun target -> (target :?> ListView).ItemDisappearing)
+ Attributes.defineEvent "ListView_ItemDisappearing" (fun target -> (target :?> ListView).ItemDisappearing)
let ItemSelected =
- Attributes.defineEvent
- "ListView_ItemSelected"
- (fun target -> (target :?> ListView).ItemSelected)
+ Attributes.defineEvent "ListView_ItemSelected" (fun target -> (target :?> ListView).ItemSelected)
let ItemTapped =
- Attributes.defineEvent
- "ListView_ItemTapped"
- (fun target -> (target :?> ListView).ItemTapped)
+ Attributes.defineEvent "ListView_ItemTapped" (fun target -> (target :?> ListView).ItemTapped)
let Refreshing =
Attributes.defineEventNoArg "ListView_Refreshing" (fun target -> (target :?> ListView).Refreshing)
@@ -105,20 +86,17 @@ module ListView =
Attributes.defineEvent "ListView_Scrolled" (fun target -> (target :?> ListView).Scrolled)
let ScrollToRequested =
- Attributes.defineEvent
- "ListView_ScrollToRequested"
- (fun target -> (target :?> ListView).ScrollToRequested)
+ Attributes.defineEvent "ListView_ScrollToRequested" (fun target -> (target :?> ListView).ScrollToRequested)
[]
module ListViewBuilders =
type Fabulous.Maui.View with
+
static member inline ListView<'msg, 'itemData, 'itemMarker when 'itemMarker :> ICell>(items: seq<'itemData>) =
- WidgetHelpers.buildItems<'msg, IListView, 'itemData, 'itemMarker>
- ListView.WidgetKey
- ItemsViewOfCell.ItemsSource
- items
+ WidgetHelpers.buildItems<'msg, IListView, 'itemData, 'itemMarker> ListView.WidgetKey ItemsViewOfCell.ItemsSource items
- static member inline GroupedListView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker when 'itemMarker :> ICell and 'groupMarker :> ICell and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
+ static member inline GroupedListView<'msg, 'groupData, 'groupMarker, 'itemData, 'itemMarker
+ when 'itemMarker :> ICell and 'groupMarker :> ICell and 'groupData :> System.Collections.Generic.IEnumerable<'itemData>>
(items: seq<'groupData>)
=
WidgetHelpers.buildGroupItemsNoFooter<'msg, IListView, 'groupData, 'itemData, 'groupMarker, 'itemMarker>
@@ -150,19 +128,11 @@ type ListViewModifiers =
this.AddScalar(ListView.HasUnevenRows.WithValue(value))
[]
- static member inline horizontalScrollBarVisibility
- (
- this: WidgetBuilder<'msg, #IListView>,
- value: ScrollBarVisibility
- ) =
+ static member inline horizontalScrollBarVisibility(this: WidgetBuilder<'msg, #IListView>, value: ScrollBarVisibility) =
this.AddScalar(ListView.HorizontalScrollBarVisibility.WithValue(value))
[]
- static member inline verticalScrollBarVisibility
- (
- this: WidgetBuilder<'msg, #IListView>,
- value: ScrollBarVisibility
- ) =
+ static member inline verticalScrollBarVisibility(this: WidgetBuilder<'msg, #IListView>, value: ScrollBarVisibility) =
this.AddScalar(ListView.VerticalScrollBarVisibility.WithValue(value))
[]
@@ -194,19 +164,11 @@ type ListViewModifiers =
this.AddScalar(ListView.SelectionMode.WithValue(value))
[]
- static member inline onItemAppearing
- (
- this: WidgetBuilder<'msg, #IListView>,
- onItemAppearing: ItemVisibilityEventArgs -> 'msg
- ) =
+ static member inline onItemAppearing(this: WidgetBuilder<'msg, #IListView>, onItemAppearing: ItemVisibilityEventArgs -> 'msg) =
this.AddScalar(ListView.ItemAppearing.WithValue(fun args -> onItemAppearing args |> box))
[]
- static member inline onItemDisappearing
- (
- this: WidgetBuilder<'msg, #IListView>,
- onItemDisappearing: ItemVisibilityEventArgs -> 'msg
- ) =
+ static member inline onItemDisappearing(this: WidgetBuilder<'msg, #IListView>, onItemDisappearing: ItemVisibilityEventArgs -> 'msg) =
this.AddScalar(ListView.ItemDisappearing.WithValue(fun args -> onItemDisappearing args |> box))
[]
@@ -214,11 +176,7 @@ type ListViewModifiers =
this.AddScalar(ListView.ItemTapped.WithValue(fun args -> onItemTapped args.ItemIndex |> box))
[ 'msg) instead")>]
- static member inline onItemSelected
- (
- this: WidgetBuilder<'msg, #IListView>,
- onItemSelected: SelectedItemChangedEventArgs -> 'msg
- ) =
+ static member inline onItemSelected(this: WidgetBuilder<'msg, #IListView>, onItemSelected: SelectedItemChangedEventArgs -> 'msg) =
this.AddScalar(ListView.ItemSelected.WithValue(fun args -> onItemSelected args |> box))
[]
@@ -234,11 +192,7 @@ type ListViewModifiers =
this.AddScalar(ListView.Scrolled.WithValue(fun args -> onScrolled args |> box))
[]
- static member inline onScrollToRequested
- (
- this: WidgetBuilder<'msg, #IListView>,
- onScrollToRequested: ScrollToRequestedEventArgs -> 'msg
- ) =
+ static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IListView>, onScrollToRequested: ScrollToRequestedEventArgs -> 'msg) =
this.AddScalar(ListView.ScrollToRequested.WithValue(fun args -> onScrollToRequested args |> box))
/// Link a ViewRef to access the direct ListView control instance
diff --git a/src/Fabulous.MauiControls/Views/Collections/_ItemsView.fs b/src/Fabulous.MauiControls/Views/Collections/_ItemsView.fs
index 6bcddbe..3fa37c6 100644
--- a/src/Fabulous.MauiControls/Views/Collections/_ItemsView.fs
+++ b/src/Fabulous.MauiControls/Views/Collections/_ItemsView.fs
@@ -21,25 +21,17 @@ module ItemsView =
itemsView.ClearValue(ItemsView.ItemTemplateProperty)
itemsView.ClearValue(ItemsView.ItemsSourceProperty)
| ValueSome value ->
- itemsView.SetValue(
- ItemsView.ItemTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.Template)
- )
+ itemsView.SetValue(ItemsView.ItemTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.Template))
itemsView.SetValue(ItemsView.ItemsSourceProperty, value.OriginalItems))
- let EmptyView =
- Attributes.defineBindableWidget ItemsView.EmptyViewProperty
+ let EmptyView = Attributes.defineBindableWidget ItemsView.EmptyViewProperty
let RemainingItemsThreshold =
Attributes.defineBindableInt ItemsView.RemainingItemsThresholdProperty
let RemainingItemsThresholdReached =
- Attributes.defineEventNoArg
- "ItemsView_RemainingItemsThresholdReached"
- (fun target ->
- (target :?> ItemsView)
- .RemainingItemsThresholdReached)
+ Attributes.defineEventNoArg "ItemsView_RemainingItemsThresholdReached" (fun target -> (target :?> ItemsView).RemainingItemsThresholdReached)
let HorizontalScrollBarVisibility =
Attributes.defineBindableEnum ItemsView.HorizontalScrollBarVisibilityProperty
@@ -51,14 +43,10 @@ module ItemsView =
Attributes.defineBindableEnum ItemsView.ItemsUpdatingScrollModeProperty
let ScrollToRequested =
- Attributes.defineEvent
- "ItemsView_ScrolledRequested"
- (fun target -> (target :?> ItemsView).ScrollToRequested)
+ Attributes.defineEvent "ItemsView_ScrolledRequested" (fun target -> (target :?> ItemsView).ScrollToRequested)
let Scrolled =
- Attributes.defineEvent
- "ItemsView_Scrolled"
- (fun target -> (target :?> ItemsView).Scrolled)
+ Attributes.defineEvent "ItemsView_Scrolled" (fun target -> (target :?> ItemsView).Scrolled)
[]
type ItemsViewModifiers =
@@ -67,12 +55,7 @@ type ItemsViewModifiers =
/// The threshold of items not yet visible in the list
/// Event executed when the RemainingItemsThreshold is reached
[]
- static member inline remainingItemsThreshold
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- value: int,
- onThresholdReached: 'msg
- ) =
+ static member inline remainingItemsThreshold(this: WidgetBuilder<'msg, #IItemsView>, value: int, onThresholdReached: 'msg) =
this
.AddScalar(ItemsView.RemainingItemsThreshold.WithValue(value))
.AddScalar(ItemsView.RemainingItemsThresholdReached.WithValue(onThresholdReached))
@@ -80,45 +63,25 @@ type ItemsViewModifiers =
/// Sets the visibility of the horizontal scroll bar.
/// true if the horizontal scroll is enabled; otherwise, false.
[]
- static member inline horizontalScrollBarVisibility
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- value: ScrollBarVisibility
- ) =
+ static member inline horizontalScrollBarVisibility(this: WidgetBuilder<'msg, #IItemsView>, value: ScrollBarVisibility) =
this.AddScalar(ItemsView.HorizontalScrollBarVisibility.WithValue(value))
/// Sets the visibility of the vertical scroll bar.
/// true if the vertical scroll is enabled; otherwise, false.
[]
- static member inline verticalScrollBarVisibility
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- value: ScrollBarVisibility
- ) =
+ static member inline verticalScrollBarVisibility(this: WidgetBuilder<'msg, #IItemsView>, value: ScrollBarVisibility) =
this.AddScalar(ItemsView.VerticalScrollBarVisibility.WithValue(value))
[]
- static member inline itemsUpdatingScrollMode
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- value: ItemsUpdatingScrollMode
- ) =
+ static member inline itemsUpdatingScrollMode(this: WidgetBuilder<'msg, #IItemsView>, value: ItemsUpdatingScrollMode) =
this.AddScalar(ItemsView.ItemsUpdatingScrollMode.WithValue(value))
[]
- static member inline onScrollToRequested
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- onScrollToRequested: ScrollToRequestEventArgs -> 'msg
- ) =
+ static member inline onScrollToRequested(this: WidgetBuilder<'msg, #IItemsView>, onScrollToRequested: ScrollToRequestEventArgs -> 'msg) =
this.AddScalar(ItemsView.ScrollToRequested.WithValue(fun args -> onScrollToRequested args |> box))
[]
- static member inline onScrolled
- (
- this: WidgetBuilder<'msg, #IItemsView>,
- onScrolled: ItemsViewScrolledEventArgs -> 'msg
- ) =
+ static member inline onScrolled(this: WidgetBuilder<'msg, #IItemsView>, onScrolled: ItemsViewScrolledEventArgs -> 'msg) =
this.AddScalar(ItemsView.Scrolled.WithValue(fun args -> onScrolled args |> box))
[]
diff --git a/src/Fabulous.MauiControls/Views/Collections/_ItemsViewOfCell.fs b/src/Fabulous.MauiControls/Views/Collections/_ItemsViewOfCell.fs
index 5179868..7c31197 100644
--- a/src/Fabulous.MauiControls/Views/Collections/_ItemsViewOfCell.fs
+++ b/src/Fabulous.MauiControls/Views/Collections/_ItemsViewOfCell.fs
@@ -20,9 +20,6 @@ module ItemsViewOfCell =
itemsView.ClearValue(ItemsView.ItemTemplateProperty)
itemsView.ClearValue(ItemsView.ItemsSourceProperty)
| ValueSome value ->
- itemsView.SetValue(
- ItemsView.ItemTemplateProperty,
- WidgetDataTemplateSelector(node, unbox >> value.Template)
- )
+ itemsView.SetValue(ItemsView.ItemTemplateProperty, WidgetDataTemplateSelector(node, unbox >> value.Template))
itemsView.SetValue(ItemsView.ItemsSourceProperty, value.OriginalItems))
diff --git a/src/Fabulous.MauiControls/Views/Controls/ActivityIndicator.fs b/src/Fabulous.MauiControls/Views/Controls/ActivityIndicator.fs
index d634ad7..f0ed90a 100644
--- a/src/Fabulous.MauiControls/Views/Controls/ActivityIndicator.fs
+++ b/src/Fabulous.MauiControls/Views/Controls/ActivityIndicator.fs
@@ -10,20 +10,16 @@ type IActivityIndicator =
module ActivityIndicator =
let WidgetKey = Widgets.register()
- let IsRunning =
- Attributes.defineBindableBool ActivityIndicator.IsRunningProperty
+ let IsRunning = Attributes.defineBindableBool ActivityIndicator.IsRunningProperty
- let Color =
- Attributes.defineBindableAppThemeColor ActivityIndicator.ColorProperty
+ let Color = Attributes.defineBindableAppThemeColor ActivityIndicator.ColorProperty
[]
module ActivityIndicatorBuilders =
type Fabulous.Maui.View with
+
static member inline ActivityIndicator<'msg>(isRunning: bool) =
- WidgetBuilder<'msg, IActivityIndicator>(
- ActivityIndicator.WidgetKey,
- ActivityIndicator.IsRunning.WithValue(isRunning)
- )
+ WidgetBuilder<'msg, IActivityIndicator>(ActivityIndicator.WidgetKey, ActivityIndicator.IsRunning.WithValue(isRunning))
[]
type ActivityIndicatorModifiers =
diff --git a/src/Fabulous.MauiControls/Views/Controls/Border.fs b/src/Fabulous.MauiControls/Views/Controls/Border.fs
index 33c7aa0..bda67c9 100644
--- a/src/Fabulous.MauiControls/Views/Controls/Border.fs
+++ b/src/Fabulous.MauiControls/Views/Controls/Border.fs
@@ -13,73 +13,50 @@ type IBorder =
module Border =
let WidgetKey = Widgets.register()
- let Stroke =
- Attributes.defineBindableAppTheme Border.StrokeProperty
+ let Stroke = Attributes.defineBindableAppTheme Border.StrokeProperty
- let StrokeWidget =
- Attributes.defineBindableWidget Border.StrokeProperty
+ let StrokeWidget = Attributes.defineBindableWidget Border.StrokeProperty
- let Content =
- Attributes.defineBindableWidget Border.ContentProperty
+ let Content = Attributes.defineBindableWidget Border.ContentProperty
let StrokeShape =
- Attributes.defineSimpleScalarWithEquality
- "Border_StrokeShape"
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
+ Attributes.defineSimpleScalarWithEquality "Border_StrokeShape" (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
- match newValueOpt with
- | ValueNone -> target.ClearValue(Border.StrokeShapeProperty)
- | ValueSome value -> target.SetValue(Border.StrokeShapeProperty, value))
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(Border.StrokeShapeProperty)
+ | ValueSome value -> target.SetValue(Border.StrokeShapeProperty, value))
let StrokeShapeString =
- Attributes.defineSimpleScalarWithEquality
- "Border_StrokeShapeString"
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
-
- match newValueOpt with
- | ValueNone -> target.ClearValue(Border.StrokeShapeProperty)
- | ValueSome value ->
- target.SetValue(
- Border.StrokeShapeProperty,
- StrokeShapeTypeConverter()
- .ConvertFromInvariantString(value)
- ))
-
- let StrokeShapeWidget =
- Attributes.defineBindableWidget Border.StrokeShapeProperty
-
- let StrokeThickness =
- Attributes.defineBindableFloat Border.StrokeThicknessProperty
+ Attributes.defineSimpleScalarWithEquality "Border_StrokeShapeString" (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
+
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(Border.StrokeShapeProperty)
+ | ValueSome value -> target.SetValue(Border.StrokeShapeProperty, StrokeShapeTypeConverter().ConvertFromInvariantString(value)))
+
+ let StrokeShapeWidget = Attributes.defineBindableWidget Border.StrokeShapeProperty
+
+ let StrokeThickness = Attributes.defineBindableFloat Border.StrokeThicknessProperty
let StrokeDashArrayString =
- Attributes.defineSimpleScalarWithEquality
- "Border_StrokeDashArrayString"
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
-
- match newValueOpt with
- | ValueNone -> target.ClearValue(Border.StrokeDashArrayProperty)
- | ValueSome string ->
- target.SetValue(
- Shape.StrokeDashArrayProperty,
- DoubleCollectionConverter()
- .ConvertFromInvariantString(string)
- ))
+ Attributes.defineSimpleScalarWithEquality "Border_StrokeDashArrayString" (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
+
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(Border.StrokeDashArrayProperty)
+ | ValueSome string -> target.SetValue(Shape.StrokeDashArrayProperty, DoubleCollectionConverter().ConvertFromInvariantString(string)))
let StrokeDashArrayList =
- Attributes.defineSimpleScalarWithEquality
- "Border_StrokeDashArrayList"
- (fun _ newValueOpt node ->
- let target = node.Target :?> BindableObject
-
- match newValueOpt with
- | ValueNone -> target.ClearValue(Border.StrokeDashArrayProperty)
- | ValueSome points ->
- let coll = DoubleCollection()
- points |> List.iter coll.Add
- target.SetValue(Border.StrokeDashArrayProperty, coll))
+ Attributes.defineSimpleScalarWithEquality "Border_StrokeDashArrayList" (fun _ newValueOpt node ->
+ let target = node.Target :?> BindableObject
+
+ match newValueOpt with
+ | ValueNone -> target.ClearValue(Border.StrokeDashArrayProperty)
+ | ValueSome points ->
+ let coll = DoubleCollection()
+ points |> List.iter coll.Add
+ target.SetValue(Border.StrokeDashArrayProperty, coll))
let StrokeDashOffset =
Attributes.defineBindableFloat Border.StrokeDashOffsetProperty
@@ -99,15 +76,11 @@ module Border =
[]
module BorderBuilders =
type Fabulous.Maui.View with
+
/// Border is a container control that draws a border, background, or both, around another control. A Border can only contain one child object. If you want to put a border around multiple objects, wrap them in a container object such as a layout
/// The color of the stroke in the light theme.
/// The color of the stroke in the dark theme.
- static member inline Border<'msg, 'marker when 'marker :> Fabulous.Maui.IView>
- (
- content: WidgetBuilder<'msg, 'marker>,
- light: Brush,
- ?dark: Brush
- ) =
+ static member inline Border<'msg, 'marker when 'marker :> Fabulous.Maui.IView>(content: WidgetBuilder<'msg, 'marker>, light: Brush, ?dark: Brush) =
WidgetBuilder<'msg, IBorder>(
Border.WidgetKey,
AttributesBundle(
@@ -133,8 +106,9 @@ module BorderBuilders =
AttributesBundle(
// By spec we need to set StrokeShape to Rectangle
StackList.one(Border.StrokeShape.WithValue(Rectangle())),
- ValueSome [| Border.Content.WithValue(content.Compile())
- Border.StrokeWidget.WithValue(stroke.Compile()) |],
+ ValueSome
+ [| Border.Content.WithValue(content.Compile())
+ Border.StrokeWidget.WithValue(stroke.Compile()) |],
ValueNone
)
)
@@ -191,14 +165,7 @@ type BorderModifiers =
BorderModifiers.padding(this, Thickness(value))
[]
- static member inline padding
- (
- this: WidgetBuilder<'msg, #IBorder>,
- left: float,
- top: float,
- right: float,
- bottom: float
- ) =
+ static member inline padding(this: WidgetBuilder<'msg, #IBorder>, left: float, top: float, right: float, bottom: float) =
BorderModifiers.padding(this, Thickness(left, top, right, bottom))
/// Link a ViewRef to access the direct Border control instance
diff --git a/src/Fabulous.MauiControls/Views/Controls/BoxView.fs b/src/Fabulous.MauiControls/Views/Controls/BoxView.fs
index 02551ac..f2a5e30 100644
--- a/src/Fabulous.MauiControls/Views/Controls/BoxView.fs
+++ b/src/Fabulous.MauiControls/Views/Controls/BoxView.fs
@@ -10,15 +10,14 @@ type IBoxView =
module BoxView =
let WidgetKey = Widgets.register()
- let Color =
- Attributes.defineBindableAppThemeColor BoxView.ColorProperty
+ let Color = Attributes.defineBindableAppThemeColor BoxView.ColorProperty
- let CornerRadius =
- Attributes.defineBindableFloat BoxView.CornerRadiusProperty
+ let CornerRadius = Attributes.defineBindableFloat BoxView.CornerRadiusProperty
[]
module BoxViewBuilders =
type Fabulous.Maui.View with
+
static member inline BoxView<'msg>(light: FabColor, ?dark: FabColor) =
WidgetBuilder<'msg, IBoxView>(BoxView.WidgetKey, BoxView.Color.WithValue(AppTheme.create light dark))
diff --git a/src/Fabulous.MauiControls/Views/Controls/Button.fs b/src/Fabulous.MauiControls/Views/Controls/Button.fs
index fbb6b67..4fa3f71 100644
--- a/src/Fabulous.MauiControls/Views/Controls/Button.fs
+++ b/src/Fabulous.MauiControls/Views/Controls/Button.fs
@@ -13,11 +13,9 @@ type IButton =
module Button =
let WidgetKey = Widgets.register | | | | |