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.MauiControls +# 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