diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
new file mode 100644
index 00000000..16bceb22
--- /dev/null
+++ b/.github/workflows/develop.yml
@@ -0,0 +1,69 @@
+name: Run the documentation generation tool and push changes to develop branch
+
+on:
+ push:
+ branches: [ "develop" ]
+
+permissions:
+ contents: write
+
+env:
+ SLN_FILE_PATH: 'HorusStudio.Maui.MaterialDesignControls.sln'
+ CSPROJ_FILE_PATH: 'src/HorusStudio.Maui.MaterialDesignControls/HorusStudio.Maui.MaterialDesignControls.csproj'
+ MATERIALDESIGNCONTROLS_DLL_FILE_PATH: 'src/HorusStudio.Maui.MaterialDesignControls/bin/Release/net9.0-android/HorusStudio.Maui.MaterialDesignControls.dll'
+ XMLDOC2MARKDOWN_FILE_PATH: 'tools/XMLDoc2Markdown/XMLDoc2Markdown.dll'
+ DOCUMENTATION_OUTPUT_PATH: 'docs'
+
+jobs:
+ build:
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
+ include-prerelease: true
+
+ - name: Install MAUI workloads
+ run: |
+ echo "📦 Installing MAUI workloads..."
+ dotnet workload install maui
+ dotnet workload restore
+ echo "✅ MAUI workloads installed successfully!"
+
+ - name: Restore dependencies
+ run: |
+ echo "📦 Restoring dependencies..."
+ dotnet restore ${{ env.SLN_FILE_PATH }}
+ echo "✅ Dependencies restored successfully!"
+
+ - name: Build solution
+ run: |
+ echo "📦 Building solution..."
+ dotnet build ${{ env.CSPROJ_FILE_PATH }} -c Release --no-restore
+ echo "✅ Solution built successfully!"
+
+ - name: Unzip the documentation generation tool
+ run: |
+ echo "📦 Unzipping the documentation generation tool..."
+ unzip tools/XMLDoc2Markdown.zip -d tools/
+ echo "✅ Documentation generation tool unzipped successfully!"
+
+ - name: Run the documentation generation tool
+ run: |
+ echo "📦 Running the documentation generation tool..."
+ dotnet ${{ env.XMLDOC2MARKDOWN_FILE_PATH }} ${{ env.MATERIALDESIGNCONTROLS_DLL_FILE_PATH }} ${{ env.DOCUMENTATION_OUTPUT_PATH }} --exclude-class-static="HorusStudio.Maui.MaterialDesignControls.MaterialLightTheme,HorusStudio.Maui.MaterialDesignControls.MaterialDarkTheme,HorusStudio.Maui.MaterialDesignControls.MaterialFontSize,HorusStudio.Maui.MaterialDesignControls.MaterialFontFamily,HorusStudio.Maui.MaterialDesignControls.MaterialElevation,HorusStudio.Maui.MaterialDesignControls.MaterialAnimation,HorusStudio.Maui.MaterialDesignControls.MaterialFontTracking,HorusStudio.Maui.MaterialDesignControls.MaterialIcon,HorusStudio.Maui.MaterialDesignControls.MaterialFormat" --excludeinternal --onlyinternal-members --no-index-page --specific-namespace="HorusStudio.Maui.MaterialDesignControls"
+ echo "✅ Documentation generated successfully!"
+
+ - name: Commit & push changes
+ run: |
+ echo "📦 Committing and pushing changes..."
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add .
+ git commit -m "Auto-generate docs [skip ci]" || echo "No changes to commit"
+ git push origin HEAD:${GITHUB_REF#refs/heads/}
+ echo "✅ Changes committed and pushed successfully!"
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
new file mode 100644
index 00000000..3ae5aedf
--- /dev/null
+++ b/.github/workflows/master.yml
@@ -0,0 +1,88 @@
+name: Build, pack and push HorusStudio.Maui.MaterialDesignControls NuGet package
+
+on:
+ push:
+ branches: [ "master" ]
+
+permissions:
+ contents: write
+
+env:
+ SLN_FILE_PATH: 'HorusStudio.Maui.MaterialDesignControls.sln'
+ CSPROJ_FILE_PATH: 'src/HorusStudio.Maui.MaterialDesignControls/HorusStudio.Maui.MaterialDesignControls.csproj'
+
+jobs:
+ build:
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
+ include-prerelease: true
+
+ - name: Install MAUI workloads
+ run: |
+ echo "📦 Installing MAUI workloads..."
+ dotnet workload install maui
+ dotnet workload restore
+ echo "✅ MAUI workloads installed successfully!"
+
+ - name: Restore dependencies
+ run: |
+ echo "📦 Restoring dependencies..."
+ dotnet restore ${{ env.SLN_FILE_PATH }}
+ echo "✅ Dependencies restored successfully!"
+
+ - name: Build solution
+ run: |
+ echo "📦 Building solution..."
+ dotnet build ${{ env.CSPROJ_FILE_PATH }} -c Release --no-restore
+ echo "✅ Solution built successfully!"
+
+ - name: Pack NuGet package
+ run: |
+ echo "📦 Packaging NuGet package..."
+ dotnet pack ${{ env.CSPROJ_FILE_PATH }} -c Release -o ./artifacts --no-build
+ echo "✅ NuGet package packed successfully!"
+
+ - name: Publish NuGet package
+ run: |
+ echo "📦 Publishing NuGet package..."
+ dotnet nuget push "./artifacts/*.nupkg" --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate
+ echo "✅ NuGet package published successfully!"
+
+ - name: Get version from nupkg
+ id: get_version
+ run: |
+ FILE=$(ls ./artifacts/*.nupkg | head -n 1)
+ VERSION=$(echo $FILE | sed -E 's/.*\.([0-9]+\.[0-9]+\.[0-9]+)\.nupkg/\1/')
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Create Git tag
+ run: |
+ echo "📦 Creating Git tag..."
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git tag -a "v${{ steps.get_version.outputs.version }}" -m "Release v${{ steps.get_version.outputs.version }}"
+ git push origin "v${{ steps.get_version.outputs.version }}"
+ echo "✅ Git tag created and pushed successfully!"
+
+ - name: Create GitHub Release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ steps.get_version.outputs.version }}
+ release_name: ${{ steps.get_version.outputs.version }}
+ body: |
+ ### [NuGet package](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/${{ steps.get_version.outputs.version }})
+ #### New features
+ -
+ #### Bug fixes
+ -
+ draft: true
+ prerelease: false
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2e7e633c..bbbe0855 100644
--- a/.gitignore
+++ b/.gitignore
@@ -322,6 +322,7 @@ __pycache__/
# Cake - Uncomment if you are using it
tools/**
!tools/packages.config
+!tools/XMLDoc2Markdown.zip
# Tabs Studio
*.tss
diff --git a/README.md b/README.md
index 0a3288d9..6748b525 100644
--- a/README.md
+++ b/README.md
@@ -286,7 +286,7 @@ Find more usages and samples on our [sample app](samples/HorusStudio.Maui.Materi
## Release notes
-Check out our progress [here](ReleaseNotes.md).
+Check out our progress [here](https://github.com/HorusSoftwareUY/Maui.MaterialDesignControls/releases).
## Developed by
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
deleted file mode 100644
index 56555668..00000000
--- a/ReleaseNotes.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# Release Notes
-
-## Content table
-- [Versions](#versions)
- - [2.0.1](#201)
- - [2.0.0](#200)
- - [1.1.0](#110)
- - [1.0.0-preview.6](#100-preview6)
- - [1.0.0-preview.5](#100-preview5)
- - [1.0.0-preview.4](#100-preview4)
- - [1.0.0-preview.3](#100-preview3)
- - [1.0.0-preview.2](#100-preview2)
- - [1.0.0-preview.1](#100-preview1)
-
-## Versions
-### [2.0.1](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/2.0.1)
-- [MaterialChip] Add CommandParameter (optional), LeadingIconSize and TrailingIconSize
-- [MaterialInputBase] Fix visual issue on Outlined inputs when a custom font or font size is used
-### [2.0.0](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/2.0.0)
-- Update to .NET 9
-- [MaterialSegmentedButton] new control added
-- [MaterialViewGroup] new control added to group controls and handle single or multiple selection
-- [IValidableView] added for error animations
-- [MaterialTopAppBar] Add IconPadding property
-- [MaterialFloatingButton] [iOS] Fix tap issue
-- Improvements with main thread
-- [MaterialNavigationDrawer] Add IconSize property
-- [MaterialRating] Add ValueChangedCommand and ValueChanged event
-- [MaterialPicker/MaterialDatePicker/MaterialTimePicker] [iOS] Fix HorizontalTextAlignment
-- [MaterialInputBase] Don't animate on tap when control is disabled
-- [MaterialInputBase] Add AlwaysShowLabel property for Outlined type
-- [MaterialInputBase] Add ErrorAnimationType and ErrorAnimatiion properties
-- [MaterialCard] Support Transparent or null shadow
-- [BREAKING CHANGE] [MaterialChipsGroup] Remove control (replaced with MaterialViewGroup)
-- [BREAKING CHANGE] [MaterialRadioButtonsGroup] Remove control (replaced with MaterialViewGroup)
-- [BREAKING CHANGE] [MaterialChips] Rename MaterialChip
-- [BREAKING CHANGE] [MaterialChipsType] Rename to MaterialChipType
-- [BREAKING CHANGE] [MaterialChip] Remove “Unselected” VisualState, “Normal” is used instead
-- [BREAKING CHANGE] [MaterialChip] Remove “IconStateOnSelection” property
-- [BREAKING CHANGE] [MaterialChip] Add SelectionChangedCommand and SelectionChanged event
-- [BREAKING CHANGE] [ITouchable] Rename to ITouchableView
-- [BREAKING CHANGE] [MaterialButton/MaterialCard/MaterialCheckbox/MaterialChip/MaterialIconButton/MaterialNavigationDrawer/MaterialRadioButton/MaterialRating/MaterialSwitch/MaterialViewButton] Rename Animation property to TouchAnimationType
-- [BREAKING CHANGE] [MaterialButton/MaterialCard/MaterialCheckbox/MaterialChip/MaterialIconButton/MaterialNavigationDrawer/MaterialRadioButton/MaterialRating/MaterialSwitch/MaterialViewButton] Remove AnimationParameter property
-- [BREAKING CHANGE] [MaterialButton/MaterialCard/MaterialCheckbox/MaterialChip/MaterialIconButton/MaterialNavigationDrawer/MaterialRadioButton/MaterialRating/MaterialSwitch/MaterialViewButton] Rename CustomAnimation property to TouchAnimation
-- [BREAKING CHANGE] [MaterialTopAppBar] Rename IconButtonAnimation property to IconButtonTouchAnimationType
-- [BREAKING CHANGE] [MaterialTopAppBar] Remove IconButtonAnimationParameter property
-- [BREAKING CHANGE] [MaterialTopAppBar] Rename IconButtonCustomAnimation property to IconButtonTouchAnimation
-- [Issue #93] Fix NullReferenceException thrown when applying implicit styles
-
-### [1.1.0](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.1.0)
-* Configure if icon tint color should be automatically set to TextColor or not on: MaterialButton, MaterialChips, MaterialIconButton and MaterialNavigationDrawerItem
-* Fix TextChanged and TextChangedCommand not being fired on MaterialTextField and MaterialMultilineTextField
-* Expose internal MAUI control used on: MaterialButton, MaterialCheckBox, MaterialDatePicker, MaterialMultilineTextField, MaterialPicker, MaterialRadioButton, MaterialSlider, MaterialTextField and MaterialTimePicker
-* Fix spacing on MaterialRating when label is empty
-* Fix touch bubbling on iOS for: MaterialButton, MaterialDatePicker, MaterialIconButton, MaterialMultilineTextField, MaterialPicker, MaterialSwitch, MaterialTextField and MaterialTimePicker
-* Ability to override custom handlers for: MaterialButton, MaterialRadioButton, MaterialTextField, MaterialTimePicker, MaterialDatePicker, MaterialPicker, MaterialMultilineTextField, MaterialCheckbox and MaterialSlider
-
-### [1.0.0-preview.6](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.6)
-* Add LineBreakMode to inputs placeholder
-* Add TextDecorations to Snackbar action
-* [iOS] Bug: Fix Snackbar background color
-
-### [1.0.0-preview.5](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.5)
-* Bug: Outlined inputs not rendered when overriding global style
-* Bug: Tint color fixed on MaterialIconButton
-* Bug: ConfigureSnackbar throw NullReferenceException if BackgroundColor was not set
-* [iOS] Bug: Leading and trailing icons width on Snackbar
-
-### [1.0.0-preview.4](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.4)
-* [Android] Bug #49: MaterialFloatingButton does not appear.
-
-### [1.0.0-preview.3](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.3)
-* Fix events on MaterialButton.
-* Fix touch on MaterialCard.
-* Fix events on MaterialIconButton.
-* Expand touchable area on MaterialSwitch.
-* Add events to MaterialViewButton.
-* Fix default icons on MaterialPicker, MaterialDatePicker and MaterialTimePicker.
-* Handle image loading error on MaterialIconButton.
-* Support iOS/MacCatalyst 17.0+.
-
-### [1.0.0-preview.2](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.2)
-* Bug fixing and improvements.
-
-### [1.0.0-preview.1](https://www.nuget.org/packages/HorusStudio.Maui.MaterialDesignControls/1.0.0-preview.1)
-First release for Android, iOS and MacCatalyst containing:
-* MaterialBadge
-* MaterialButton
-* MaterialCard
-* MaterialCheckbox
-* MaterialChips
-* MaterialChipsGroup
-* MaterialDatePicker
-* MaterialDivider
-* MaterialFloatingButton
-* MaterialIconButton
-* MaterialLabel
-* MaterialMultilineTextField
-* MaterialNavigationDrawer
-* MaterialPicker
-* MaterialProgressIndicator
-* MaterialRadioButton
-* MaterialRating
-* MaterialSelection
-* MaterialSlider
-* MaterialSwitch
-* MaterialSnackbar
-* MaterialTextField
-* MaterialTimePicker
-* MaterialTopAppBar
-* MaterialViewButton
-
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.imaterialsnackbar.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.imaterialsnackbar.md
index 1191d805..4d9408af 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.imaterialsnackbar.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.imaterialsnackbar.md
@@ -30,4 +30,5 @@ private async void DefaultSnackbar()
## Known issues and pending features
-* [macOS] MaterialSnackbar with icons are not shown due to an icon rendering issue
+* [macOS] MaterialSnackbar with icons are not shown due to an icon rendering issue.
+ * Add FontFamily, TextTransform and TextDecorations properties.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.itouchableview.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.itouchableview.md
new file mode 100644
index 00000000..58efdef4
--- /dev/null
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.itouchableview.md
@@ -0,0 +1,48 @@
+# ITouchableView
+
+The ITouchableView interface serves as a base for touchable views.
+
+Namespace: HorusStudio.Maui.MaterialDesignControls
+
+
+
+## Properties
+
+### **IsEnabled**
+
+Gets or sets the state when the view is enabled.
+
+Property type: [Boolean](https://learn.microsoft.com/en-us/dotnet/api/system.boolean)
+
+
+
+### **TouchAnimation**
+
+Gets or sets a custom animation to be executed when view is clicked.
+
+Property type: ITouchAnimation
+
+
+
+### **TouchAnimationType**
+
+Gets or sets an animation to be executed when view is clicked.
+
+Property type: TouchAnimationTypes
+
+| Name | Value | Description |
+| --- | --: | --- |
+| None | 0 | None: no animation runs. |
+| Fade | 1 | Fade: Represents an animation that simulates a "fade" effect by changing the opacity over the target element. |
+| Scale | 2 | Scale: Represents an animation that simulates a "sink" or "sunken" effect by scaling the target element. |
+| Bounce | 3 | Bounce: Represents an animation that simulates a "sink" or "sunken" effect with a "bounce" effect when the user releases the target element. |
+
+
+
+## Events
+
+### **Touch**
+
+Occurs when the view is touched.
+
+
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialbutton.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialbutton.md
index bc305d6a..58c930dc 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialbutton.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialbutton.md
@@ -429,6 +429,12 @@ Occurs when the button is released.
+### **Touch**
+
+Occurs when the button is touched.
+
+
+
### **Unfocused**
Occurs when the button is unfocused.
@@ -442,3 +448,4 @@ Occurs when the button is unfocused.
* ContentLayout is buggy.
* Add default Material behavior for pressed state on default styles (v2).
* [iOS] FontAttributes and SupportingFontAttributes don't work (MAUI issue)
+ * [Android] When placed inside a ScrollView, it will fire Command on drag event as well. This behavior is totally inherited from MAUI Button. If you need to avoid that, we recommend using MaterialCard, MaterialChip, or MaterialViewButton instead.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcard.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcard.md
index a4c17ea3..de3bfd47 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcard.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcard.md
@@ -200,6 +200,12 @@ Occurs when the card is released.
+### **Touch**
+
+Occurs when the card is touched.
+
+
+
## Known issues and pending features
* Disable color styles looks a bit weird with the opacities that the guideline specifies, we have to review them.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcheckbox.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcheckbox.md
index c50bfd09..24bad8d4 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcheckbox.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialcheckbox.md
@@ -241,6 +241,12 @@ Occurs when the checkbox is checked / unchecked
+### **Touch**
+
+Occurs when the checkbox is touched.
+
+
+
## Known issues and pending features
* [iOS] FontAttributes doesn't work.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialchip.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialchip.md
index d55533a5..84d9954f 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialchip.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialchip.md
@@ -379,3 +379,9 @@ Remarks: If a value is not explicitly set, the control will use the Text propert
Occurs when the chips is clicked/tapped.
+
+### **Touch**
+
+Occurs when the chip is touched.
+
+
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialiconbutton.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialiconbutton.md
index 055a470b..f54837db 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialiconbutton.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialiconbutton.md
@@ -315,6 +315,12 @@ Occurs when the button is released.
+### **Touch**
+
+Occurs when the icon button is touched.
+
+
+
## Known issues and pending features
* Shadow doesn't react to VisualStateManager changes.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialradiobutton.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialradiobutton.md
index 25f68e69..63d7262f 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialradiobutton.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialradiobutton.md
@@ -255,6 +255,12 @@ Occurs when the radio button is switched
+### **Touch**
+
+Occurs when the radio button is touched.
+
+
+
## Known issues and pending features
* [iOS] FontAttributes doesn't work.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsegmentedbutton.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsegmentedbutton.md
index da54df0e..58158841 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsegmentedbutton.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsegmentedbutton.md
@@ -237,6 +237,12 @@ Occurs when the selection of one of the segmented buttons changes.
+### **Touch**
+
+Occurs when the segmented button is touched.
+
+
+
## Known issues and pending features
* [iOS] FontAttributes doesn't work (MAUI issue)
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsnackbarconfig.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsnackbarconfig.md
index dde578f2..0fb22f6f 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsnackbarconfig.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialsnackbarconfig.md
@@ -46,6 +46,16 @@ Default value: Light: MaterialLightTheme.InverseSurface - Dark: MaterialDarkThem
+### **CharacterSpacing**
+
+Gets or sets character spacing for snackbar.
+
+Property type: [Double](https://learn.microsoft.com/en-us/dotnet/api/system.double)
+
+Default value: 0
+
+
+
### **CornerRadius**
Gets or sets corner radius for snackbar
@@ -96,6 +106,16 @@ Default value: Light: MaterialLightTheme.InverseSurface - Dark: MaterialDarkThem
+### **DefaultCharacterSpacing**
+
+Text character spacing to be applied by default to every Snackbar that doesn't set one.
+
+Property type: [Double](https://learn.microsoft.com/en-us/dotnet/api/system.double)
+
+Default value: 0
+
+
+
### **DefaultCornerRadius**
Corner radius to be applied by default to every Snackbar that doesn't set one.
@@ -116,6 +136,17 @@ Default value: 3 seconds
+### **DefaultFontAttributes**
+
+Gets or sets a value that indicates whether the font for the text of this snackbar is bold, italic, or neither.
+ This is a bindable property.
+
+Property type: [FontAttributes](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.fontattributes)
+
+Default value: FontAttributes.None
+
+
+
### **DefaultFontSize**
Text font size to be applied by default to every Snackbar that doesn't set one.
@@ -146,6 +177,16 @@ Default value: 24
+### **DefaultLineBreakMode**
+
+Determines how Text is shown by default when the length is overflowing the size of this snackbar.
+
+Property type: [LineBreakMode](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.linebreakmode)
+
+Default value: LineBreakMode.WordWrap
+
+
+
### **DefaultMargin**
Margin to be applied by default to every Snackbar that doesn't set one.
@@ -211,6 +252,17 @@ Default value: 3 seconds
+### **FontAttributes**
+
+Gets or sets a value that indicates whether the font for the text of this snackbar is bold, italic, or neither.
+ This is a bindable property.
+
+Property type: [FontAttributes](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.fontattributes)
+
+Default value: FontAttributes.None
+
+
+
### **FontSize**
Gets or sets text size for snackbar.
@@ -231,6 +283,16 @@ Default value: null
+### **LineBreakMode**
+
+Determines how Text is shown when the length is overflowing the size of this snackbar.
+
+Property type: [LineBreakMode](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.linebreakmode)
+
+Default value: LineBreakMode.WordWrap
+
+
+
### **Margin**
Gets or sets snackbar margin.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialswitch.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialswitch.md
index 41574630..07f159fd 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialswitch.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialswitch.md
@@ -310,6 +310,20 @@ Property type: [ImageSource](https://learn.microsoft.com/en-us/dotnet/api/micros
+## Events
+
+### **Toggled**
+
+Occurs when the switch is toggled.
+
+
+
+### **Touch**
+
+Occurs when the switch is touched.
+
+
+
## Known issues and pending features
* Track color animation: change from on-track color to off-track color within the toggle animation.
diff --git a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialviewbutton.md b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialviewbutton.md
index 186dad6e..a8cfeecb 100644
--- a/docs/Controls/horusstudio.maui.materialdesigncontrols.materialviewbutton.md
+++ b/docs/Controls/horusstudio.maui.materialdesigncontrols.materialviewbutton.md
@@ -82,3 +82,9 @@ Occurs when the card is pressed.
Occurs when the card is released.
+
+### **Touch**
+
+Occurs when the card is touched.
+
+
diff --git a/nuget.props b/nuget.props
index 28037003..28e873f1 100644
--- a/nuget.props
+++ b/nuget.props
@@ -1,7 +1,7 @@
HorusStudio.Maui.MaterialDesignControls
- 2.0.1
+ 2.1.0
Horus Studio and contributors
Horus Studio
Material Design Controls for .NET MAUI
@@ -15,8 +15,12 @@
dotnet-maui;dotnet;maui;cross-platform;android;ios;macos;maccatalyst;materialdesign;ui
README.md
-- [MaterialChip] Add CommandParameter (optional), LeadingIconSize and TrailingIconSize
-- [MaterialInputBase] Fix visual issue on Outlined inputs when a custom font or font size is used
+- [BREAKING CHANGE] Change ITouchableView namespace to HorusStudio.Maui.MaterialDesignControls
+- [ITouchableView] Add new Touch event and, therefore, to MaterialButton, MaterialCard, MaterialCheckBox, MaterialChip, MaterialIconButton, MaterialRadioButton, MaterialSegmentedButton, MaterialSwitch and MaterialViewButton
+- [MaterialCheckbox] Fix touch animation
+- [MaterialChip] Fix visual issue when dragging it remained as pressed
+- [MaterialSnackbar] Add support for CharacterSpacing, LineBreakMode and FontAttributes
+- [Android] [MaterialSnackbar] Show on top of all content
$(MSBuildThisFileDirectory)Artifacts
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml
index 57b46e6d..708d7065 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml
@@ -339,6 +339,12 @@
Type="Tonal"
Text="Release me"
Released="OnCounterReleased" />
+
+
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml.cs b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml.cs
index c0cc2b10..bede845e 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml.cs
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/ButtonPage.xaml.cs
@@ -48,7 +48,12 @@ private void OnCounterReleased(object sender, EventArgs e)
System.Diagnostics.Debug.WriteLine(ReleaseBtn.Text);
}
-
+
+ private void OnTouch(object? sender, TouchEventArgs e)
+ {
+ TouchBtn.Text = $"Touch event: {e.TouchEventType}";
+ }
+
void MaterialButton_Clicked(System.Object sender, System.EventArgs e)
{
}
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml
index 8b633106..6c354dad 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml
@@ -229,5 +229,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml.cs b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml.cs
index 02377de2..00acf188 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml.cs
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/CardPage.xaml.cs
@@ -4,8 +4,25 @@ namespace HorusStudio.Maui.MaterialDesignControls.Sample.Pages;
public partial class CardPage : BaseContentPage
{
+ private int _clickedCount = 0;
+
public CardPage(CardViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}
+
+ private void MaterialCard_OnTouch(object? sender, TouchEventArgs e)
+ {
+ lblTouchEvent.Text = $"Touch event: {e.TouchEventType}";
+ }
+
+ private void MaterialCard_OnClicked(object? sender, EventArgs e)
+ {
+ _clickedCount++;
+
+ if (_clickedCount == 1)
+ lblClickedEvent.Text = $"Clicked {_clickedCount} time";
+ else
+ lblClickedEvent.Text = $"Clicked {_clickedCount} times";
+ }
}
\ No newline at end of file
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml
index 7e7c350d..709ba630 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml
@@ -224,6 +224,18 @@
Type="Tonal"
ImageSource="settings.png"
Released="OnCounterReleased" />
+
+
+
+
+
diff --git a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml.cs b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml.cs
index 8edfecb5..de47b2b7 100644
--- a/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml.cs
+++ b/samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/IconButtonPage.xaml.cs
@@ -7,6 +7,7 @@ public partial class IconButtonPage : BaseContentPage
private int _clickedCount = 0;
private int _pressedCount = 0;
private int _releasedCount = 0;
+ private int _touchCount = 0;
public IconButtonPage(IconButtonViewModel viewModel) : base(viewModel)
{
@@ -36,5 +37,10 @@ private void OnCounterReleased(object sender, EventArgs e)
System.Diagnostics.Debug.WriteLine(msg);
DisplayAlert("Events", msg, "OK");
}
+
+ private void OnTouch(object? sender, TouchEventArgs e)
+ {
+ lblTouchEvent.Text = $"Touch event: {e.TouchEventType}";
+ }
}
}
\ No newline at end of file
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/ITouchableView.cs b/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/ITouchableView.cs
index 29558824..58e847f1 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/ITouchableView.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/ITouchableView.cs
@@ -1,6 +1,6 @@
using System.Windows.Input;
-namespace HorusStudio.Maui.MaterialDesignControls.Behaviors
+namespace HorusStudio.Maui.MaterialDesignControls
{
///
/// The ITouchableView interface serves as a base for touchable views.
@@ -12,6 +12,11 @@ public interface ITouchableView
///
void OnTouch(TouchEventType touchEventType);
+ ///
+ /// Occurs when the view is touched.
+ ///
+ event EventHandler? Touch;
+
///
/// Gets or sets the state when the view is enabled.
///
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/TouchEventArgs.cs b/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/TouchEventArgs.cs
new file mode 100644
index 00000000..d18da89c
--- /dev/null
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Behaviors/Touch/TouchEventArgs.cs
@@ -0,0 +1,12 @@
+namespace HorusStudio.Maui.MaterialDesignControls;
+
+[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+public class TouchEventArgs : EventArgs
+{
+ public TouchEventType TouchEventType { get; private set; }
+
+ public TouchEventArgs(TouchEventType touchEventType)
+ {
+ TouchEventType = touchEventType;
+ }
+}
\ No newline at end of file
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Builder/MaterialSnackbarOptions.cs b/src/HorusStudio.Maui.MaterialDesignControls/Builder/MaterialSnackbarOptions.cs
index 0d8f1087..3a118cff 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Builder/MaterialSnackbarOptions.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Builder/MaterialSnackbarOptions.cs
@@ -11,6 +11,9 @@ public sealed class MaterialSnackbarOptions
public Color? DefaultBackgroundColor { get; set; }
public Color? DefaultTextColor { get; set; }
public double? DefaultFontSize { get; set; }
+ public double? DefaultCharacterSpacing { get; set; }
+ public LineBreakMode? DefaultLineBreakMode { get; set; }
+ public FontAttributes? DefaultFontAttributes { get; set; }
public Color? DefaultActionColor { get; set; }
public double? DefaultActionSize { get; set; }
public TextDecorations? DefaultActionTextDecorations { get; set; }
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/BorderButton/BorderButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/BorderButton/BorderButton.cs
index 0f9a7224..6b9a2ba7 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/BorderButton/BorderButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/BorderButton/BorderButton.cs
@@ -105,6 +105,7 @@ public ITouchAnimation TouchAnimation
private EventHandler? _clicked;
private EventHandler? _pressed;
private EventHandler? _released;
+ private EventHandler? _touch;
private readonly object _objectLock = new();
///
@@ -175,6 +176,29 @@ public event EventHandler Released
}
}
}
+
+ ///
+ /// Occurs when the border button is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ UpdateTouchBehavior();
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ UpdateTouchBehavior();
+ }
+ }
+ }
#endregion Events
@@ -186,6 +210,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+
+ _touch?.Invoke(this, new TouchEventArgs(gestureType));
switch (gestureType)
{
@@ -219,7 +245,7 @@ private void UpdateTouchBehavior()
{
var touchBehavior = Behaviors.FirstOrDefault(b => b is TouchBehavior) as TouchBehavior;
- if (Command != null || _clicked != null || _pressed != null || _released != null)
+ if (Command != null || _clicked != null || _pressed != null || _released != null || _touch != null)
{
if (touchBehavior == null)
{
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Button/MaterialButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Button/MaterialButton.cs
index 616f622b..60d54bdb 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Button/MaterialButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Button/MaterialButton.cs
@@ -63,6 +63,7 @@ public enum MaterialButtonType
/// * ContentLayout is buggy.
/// * Add default Material behavior for pressed state on default styles (v2).
/// * [iOS] FontAttributes and SupportingFontAttributes don't work (MAUI issue)
+/// * [Android] When placed inside a ScrollView, it will fire Command on drag event as well. This behavior is totally inherited from MAUI Button. If you need to avoid that, we recommend using MaterialCard, MaterialChip, or MaterialViewButton instead.
///
public class MaterialButton : ContentView, ITouchableView
{
@@ -792,6 +793,7 @@ public TextDecorations TextDecorations
private EventHandler? _clicked;
private EventHandler? _pressed;
private EventHandler? _released;
+ private EventHandler? _touch;
private readonly object _objectLock = new();
///
@@ -856,6 +858,27 @@ public event EventHandler Released
}
}
}
+
+ ///
+ /// Occurs when the button is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ }
+ }
+ }
///
/// Occurs when the button is focused.
@@ -1280,12 +1303,14 @@ public async void OnTouch(TouchEventType gestureType)
{
Utils.Logger.Debug($"Gesture: {gestureType}");
- if (!IsEnabled || (Command == null && _pressed == null && _released == null && _clicked == null))
+ if (!IsEnabled || (Command == null && _pressed == null && _released == null && _clicked == null && _touch == null))
{
return;
}
await TouchAnimationManager.AnimateAsync(this, gestureType);
+
+ _touch?.Invoke(this, new TouchEventArgs(gestureType));
if (gestureType == TouchEventType.Released)
{
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Card/MaterialCard.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Card/MaterialCard.cs
index 6c26f0d9..344cdc4e 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Card/MaterialCard.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Card/MaterialCard.cs
@@ -356,6 +356,7 @@ public float BorderWidth
private EventHandler? _clicked;
private EventHandler? _pressed;
private EventHandler? _released;
+ private EventHandler? _touch;
private readonly object _objectLock = new();
///
@@ -426,6 +427,29 @@ public event EventHandler Released
}
}
}
+
+ ///
+ /// Occurs when the card is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ UpdateTouchBehavior();
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ UpdateTouchBehavior();
+ }
+ }
+ }
#endregion Events
@@ -594,7 +618,7 @@ private void UpdateTouchBehavior()
{
var touchBehavior = Behaviors.FirstOrDefault(b => b is TouchBehavior) as TouchBehavior;
- if (Command != null || _clicked != null || _pressed != null || _released != null)
+ if (Command != null || _clicked != null || _pressed != null || _released != null || _touch != null)
{
if (touchBehavior == null)
{
@@ -618,6 +642,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+ _touch?.Invoke(this, new TouchEventArgs(gestureType));
+
switch (gestureType)
{
case TouchEventType.Pressed:
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Checkbox/MaterialCheckBox.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Checkbox/MaterialCheckBox.cs
index 938e57e4..42532794 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Checkbox/MaterialCheckBox.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Checkbox/MaterialCheckBox.cs
@@ -59,7 +59,7 @@ public class MaterialCheckBox : ContentView, ITouchableView
private readonly MaterialLabel _label;
private readonly CustomCheckBox _checkbox;
private readonly Grid _mainLayout;
- private readonly BoxView _boxView;
+ private readonly MaterialViewButton _viewButton;
#endregion Layout
@@ -431,19 +431,11 @@ public MaterialCheckBox()
_mainLayout.Children.Add(_checkbox);
#if ANDROID
- _boxView = new()
- {
- BackgroundColor = Colors.Transparent,
- Color = Colors.Transparent
- };
- _boxView.SetValue(Grid.RowProperty, 0);
- _boxView.SetValue(Grid.ColumnProperty, 0);
-
- var tapGestureRecognizer = new TapGestureRecognizer();
- tapGestureRecognizer.Tapped += OnCheckBoxTapped;
- _boxView.GestureRecognizers.Add(tapGestureRecognizer);
-
- _mainLayout.Children.Add(_boxView);
+ _viewButton = new();
+ _viewButton.SetValue(Grid.RowProperty, 0);
+ _viewButton.SetValue(Grid.ColumnProperty, 0);
+ _viewButton.Touch += OnCheckBoxTouch;
+ _mainLayout.Children.Add(_viewButton);
#endif
_checkbox.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(IsChecked), source: this));
@@ -486,6 +478,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+ Touch?.Invoke(this, new TouchEventArgs(gestureType));
+
if (gestureType == TouchEventType.Released)
{
IsChecked = !IsChecked;
@@ -501,16 +495,18 @@ public async void OnTouch(TouchEventType gestureType)
///
public event EventHandler? CheckedChanged;
+ ///
+ /// Occurs when the checkbox is touched.
+ ///
+ public event EventHandler? Touch;
+
#endregion Events
#region Methods
-
- private void OnCheckBoxTapped(object? sender, TappedEventArgs e)
+
+ private void OnCheckBoxTouch(object? sender, TouchEventArgs e)
{
- if(IsEnabled)
- {
- this.IsChecked = !this.IsChecked;
- }
+ OnTouch(e.TouchEventType);
}
private void TextSideChanged(TextSide textSide)
@@ -539,8 +535,8 @@ private void TextSideChanged(TextSide textSide)
_mainLayout.Children.Add(_label);
_mainLayout.Children.Add(_checkbox);
#if ANDROID
- _boxView.SetValue(Grid.ColumnProperty, 1);
- _mainLayout.Children.Add(_boxView);
+ _viewButton.SetValue(Grid.ColumnProperty, 1);
+ _mainLayout.Children.Add(_viewButton);
#endif
break;
case TextSide.Right:
@@ -562,8 +558,8 @@ private void TextSideChanged(TextSide textSide)
_mainLayout.Children.Add(_checkbox);
#if ANDROID
- _boxView.SetValue(Grid.ColumnProperty, 0);
- _mainLayout.Children.Add(_boxView);
+ _viewButton.SetValue(Grid.ColumnProperty, 0);
+ _mainLayout.Children.Add(_viewButton);
#endif
_mainLayout.Children.Add(_label);
break;
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Chips/MaterialChip.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Chips/MaterialChip.cs
index 7317d94f..702ab7b8 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Chips/MaterialChip.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Chips/MaterialChip.cs
@@ -623,6 +623,7 @@ public object Value
#region Events
private EventHandler? _clicked;
+ private EventHandler? _touch;
private readonly Lock _objectLock = new();
///
@@ -650,6 +651,29 @@ public event EventHandler? Clicked
}
}
}
+
+ ///
+ /// Occurs when the chip is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ _container.Touch += value;
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ _container.Touch -= value;
+ }
+ }
+ }
///
public async void OnTouch(TouchEventType gestureType)
@@ -673,31 +697,36 @@ public async void OnTouch(TouchEventType gestureType)
_clicked?.Invoke(this, new IsSelectedEventArgs(IsSelected));
}
}
-
- protected virtual void InternalPressedHandler(object? sender, EventArgs e)
+
+ protected virtual async void InternalTouchHandler(object? sender, TouchEventArgs e)
{
if (!IsEnabled) return;
- OnTouch(TouchEventType.Pressed);
- }
- protected virtual void InternalReleasedHandler(object? sender, EventArgs e)
- {
- if (!IsEnabled) return;
-
- if (Type == MaterialChipType.Normal)
+ switch (e.TouchEventType)
{
- VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
- }
- else if (GroupableViewPropertyChanged != null)
- {
- GroupableViewPropertyChanged.Invoke(this, new GroupableViewPropertyChangedEventArgs(nameof(IsSelected), IsSelected, !IsSelected));
- }
- else
- {
- IsSelected = !IsSelected;
- }
+ case TouchEventType.Pressed:
+ OnTouch(TouchEventType.Pressed);
+ break;
+ case TouchEventType.Released:
+ if (Type == MaterialChipType.Normal)
+ {
+ VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
+ }
+ else if (GroupableViewPropertyChanged != null)
+ {
+ GroupableViewPropertyChanged.Invoke(this, new GroupableViewPropertyChangedEventArgs(nameof(IsSelected), IsSelected, !IsSelected));
+ }
+ else
+ {
+ IsSelected = !IsSelected;
+ }
- OnTouch(TouchEventType.Released);
+ OnTouch(TouchEventType.Released);
+ break;
+ default:
+ await TouchAnimationManager.AnimateAsync(this, TouchEventType.Released);
+ break;
+ }
}
#endregion Events
@@ -806,9 +835,8 @@ private void CreateLayout(bool toGroup = false)
_trailingIcon.SetBinding(Image.SourceProperty, new Binding(nameof(TrailingIcon), source: this));
_trailingIcon.SetBinding(Image.WidthRequestProperty, new Binding(nameof(TrailingIconSize), source: this));
_trailingIcon.SetBinding(Image.HeightRequestProperty, new Binding(nameof(TrailingIconSize), source: this));
-
- _container.Pressed += InternalPressedHandler;
- _container.Released += InternalReleasedHandler;
+
+ _container.Touch += InternalTouchHandler;
_container.Content = _hStack;
Content = _container;
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/IconButton/MaterialIconButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/IconButton/MaterialIconButton.cs
index 52d4b3da..cbddcd39 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/IconButton/MaterialIconButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/IconButton/MaterialIconButton.cs
@@ -584,6 +584,7 @@ public bool UseIconTintColor
private EventHandler? _clicked;
private EventHandler? _pressed;
private EventHandler? _released;
+ private EventHandler? _touch;
private readonly object _objectLock = new();
///
@@ -648,6 +649,27 @@ public event EventHandler Released
}
}
}
+
+ ///
+ /// Occurs when the icon button is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ }
+ }
+ }
#endregion Events
@@ -950,6 +972,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+
+ _touch?.Invoke(this, new TouchEventArgs(gestureType));
switch (gestureType)
{
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/RadioButton/MaterialRadioButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/RadioButton/MaterialRadioButton.cs
index 10613fd8..83310809 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/RadioButton/MaterialRadioButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/RadioButton/MaterialRadioButton.cs
@@ -508,6 +508,8 @@ public async void OnTouch(TouchEventType gestureType)
await TouchAnimationManager.AnimateAsync(this, gestureType);
+ Touch?.Invoke(this, new TouchEventArgs(gestureType));
+
if (gestureType == TouchEventType.Released)
{
if (GroupableViewPropertyChanged != null)
@@ -531,6 +533,11 @@ public async void OnTouch(TouchEventType gestureType)
/// Occurs when the radio button is switched
///
public event EventHandler? CheckedChanged;
+
+ ///
+ /// Occurs when the radio button is touched.
+ ///
+ public event EventHandler? Touch;
#endregion Events
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/SegmentedButton/MaterialSegmentedButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/SegmentedButton/MaterialSegmentedButton.cs
index 4b96101a..b9aaed51 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/SegmentedButton/MaterialSegmentedButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/SegmentedButton/MaterialSegmentedButton.cs
@@ -409,10 +409,36 @@ public MaterialSegmentedButtonItem? SelectedItem
#region Events
+ private EventHandler? _touch;
+ private readonly object _objectLock = new();
+
///
/// Occurs when the selection of one of the segmented buttons changes.
///
public event EventHandler? SelectionChanged;
+
+ ///
+ /// Occurs when the segmented button is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ AddTouchEvents();
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ RemoveTouchEvents();
+ }
+ }
+ }
private void OnSegmentedButtonTap(MaterialSegmentedButtonItem segmentedButton)
{
@@ -464,6 +490,41 @@ private void InvokeSelectionChanged()
SelectionChanged?.Invoke(this, new SegmentedButtonSelectedEventArgs(SelectedItem));
}
}
+
+ protected virtual async void InternalTouchHandler(object? sender, TouchEventArgs e)
+ {
+ if (!IsEnabled) return;
+
+ _touch?.Invoke(sender, new TouchEventArgs(e.TouchEventType));
+ }
+
+ private void AddTouchEvents()
+ {
+ if (_itemsContainer.Children != null && _itemsContainer.Children.Any())
+ {
+ foreach (var itemView in _itemsContainer.Children)
+ {
+ if (itemView is ITouchableView touchableView)
+ {
+ touchableView.Touch += InternalTouchHandler;
+ }
+ }
+ }
+ }
+
+ private void RemoveTouchEvents()
+ {
+ if (_itemsContainer.Children != null && _itemsContainer.Children.Any())
+ {
+ foreach (var itemView in _itemsContainer.Children)
+ {
+ if (itemView is ITouchableView touchableView)
+ {
+ touchableView.Touch -= InternalTouchHandler;
+ }
+ }
+ }
+ }
#endregion
@@ -543,6 +604,8 @@ private void UpdateItemsSource()
{
return;
}
+
+ RemoveTouchEvents();
_itemsContainer.Children.Clear();
_itemsContainer.ColumnDefinitions = new ColumnDefinitionCollection();
@@ -609,6 +672,11 @@ private bool AddItem(MaterialSegmentedButtonItem item, DataTemplate itemTemplate
return false;
}
+ if (itemLayout is ITouchableView touchableView && _touch != null)
+ {
+ touchableView.Touch += InternalTouchHandler;
+ }
+
_itemsContainer.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(ItemsSource.Count() / 100.0, GridUnitType.Star) });
_itemsContainer.Add(itemLayout, indexColum);
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/IMaterialSnackbar.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/IMaterialSnackbar.cs
index 8159c133..23928173 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/IMaterialSnackbar.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/IMaterialSnackbar.cs
@@ -26,7 +26,8 @@ namespace HorusStudio.Maui.MaterialDesignControls;
/// [See more example](../../samples/HorusStudio.Maui.MaterialDesignControls.Sample/Pages/SnackbarPage.xaml)
///
///
-/// * [macOS] MaterialSnackbar with icons are not shown due to an icon rendering issue
+/// * [macOS] MaterialSnackbar with icons are not shown due to an icon rendering issue.
+/// * Add FontFamily, TextTransform and TextDecorations properties.
///
public interface IMaterialSnackbar
{
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.Android.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.Android.cs
index c99964d0..80cb52ae 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.Android.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.Android.cs
@@ -1,17 +1,20 @@
using Android.App;
using Android.Graphics;
+using Android.OS;
using Android.Text;
using Android.Text.Style;
using Android.Views;
using Android.Widget;
using Google.Android.Material.Snackbar;
+using HorusStudio.Maui.MaterialDesignControls.Extensions.Label;
using Microsoft.Maui.Platform;
+using Activity = Android.App.Activity;
using Button = Android.Widget.Button;
-using Color = Microsoft.Maui.Graphics.Color;
+using View = Android.Views.View;
namespace HorusStudio.Maui.MaterialDesignControls;
-class MaterialSnackbarBuilder : Snackbar.Callback
+class MaterialSnackbarBuilder
{
#region Constants
@@ -21,7 +24,8 @@ class MaterialSnackbarBuilder : Snackbar.Callback
#region Attributes
- private readonly Snackbar? _snackbar;
+ private readonly MaterialSnackbarConfig _config;
+ private readonly Dialog _dialog;
private Android.Widget.ImageButton? _leadingIconView;
private Android.Widget.ImageButton? _trailingIconView;
private TextView? _textView;
@@ -30,150 +34,167 @@ class MaterialSnackbarBuilder : Snackbar.Callback
private TaskCompletionSource? _showCompletionSource;
#endregion Attributes
-
+
public MaterialSnackbarBuilder(Activity activity, MaterialSnackbarConfig config)
{
- _onDismissed = config.OnDismissed;
+ ArgumentNullException.ThrowIfNull(config);
+ ArgumentNullException.ThrowIfNull(activity);
- var extraPadding = 12;
- config.Padding = new Thickness(config.Padding.Left, config.Padding.Top - extraPadding, config.Padding.Right, config.Padding.Bottom - extraPadding);
+ _config = config;
- _snackbar = Build(config, activity);
- _snackbar.AddCallback(this);
- }
-
- #region Snackbar.Callback
+ _onDismissed = config.OnDismissed;
+
+ var root = new LinearLayout(activity)
+ {
+ Orientation = Orientation.Horizontal,
+ LayoutParameters = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WrapContent,
+ ViewGroup.LayoutParams.WrapContent)
+ {
+ Gravity = GravityFlags.CenterVertical
+ }
+ };
+
+ var insets = root!.GetInsets();
+ root.SetRoundedBackground(config.BackgroundColor, config.CornerRadius)
+ .SetMargin(config.Margin, insets)
+ .SetPadding(config.Padding);
- public override void OnDismissed(Snackbar? control, int e)
- {
- base.OnDismissed(control, e);
+ if (config.LeadingIcon is not null)
+ {
+ _leadingIconView = activity.CreateImageButton(config.LeadingIcon.Source, config.LeadingIcon.Size,
+ config.LeadingIcon.Color, new Thickness(0), config.LeadingIcon.Action);
+ root.AddView(_leadingIconView);
+ }
- if (_showCompletionSource != null)
+ var messageContainer = new LinearLayout(activity)
{
- _showCompletionSource.SetResult();
+ Orientation = Orientation.Vertical,
+ LayoutParameters = new LinearLayout.LayoutParams(
+ 0,
+ ViewGroup.LayoutParams.WrapContent,
+ 1f)
+ {
+ Gravity = GravityFlags.CenterVertical
+ }
+ };
+ _textView = ConfigureText(activity, config);
+ messageContainer.AddView(_textView);
+ root.AddView(messageContainer);
+
+ if (config.Action is not null)
+ {
+ _actionView = ConfigureAction(activity, config.Action, Dismiss);
+ root.AddView(_actionView);
+ }
+
+ if (config.TrailingIcon is not null)
+ {
+ _trailingIconView = activity.CreateImageButton(config.TrailingIcon.Source, config.TrailingIcon.Size,
+ config.TrailingIcon.Color, new Thickness(0), config.TrailingIcon.Action);
+ root.AddView(_trailingIconView);
+ }
+
+ _textView!.SetMargin(new Thickness(_leadingIconView is not null ? config.Spacing : 0,0,0,0));
+ _actionView?.SetMargin(new Thickness(config.Spacing,0,_trailingIconView is not null ? config.Spacing : 0,0));
+
+ _dialog = new Dialog(activity);
+ _dialog.SetContentView(root);
+
+ _dialog.Window?.SetGravity(config.Position);
+
+ _dialog.DismissEvent += (s, e) =>
+ {
+ _showCompletionSource?.SetResult();
_showCompletionSource = null;
- return;
+ _onDismissed?.Invoke();
};
- _onDismissed?.Invoke();
}
- #endregion
-
- public void Show() => _snackbar?.Show();
+ public void Show()
+ {
+ _dialog?.Show();
+ SetAutomaticDismiss();
+ }
public Task ShowAsync()
{
- if (_snackbar == null) return Task.CompletedTask;
+ if (_dialog == null) return Task.CompletedTask;
_showCompletionSource = new TaskCompletionSource();
- _snackbar.Show();
+ _dialog.Show();
+ SetAutomaticDismiss();
return _showCompletionSource.Task;
}
- public void Dismiss() => _snackbar?.Dismiss();
-
- private Snackbar Build(MaterialSnackbarConfig config, Activity? activity)
+ private void SetAutomaticDismiss()
{
- ArgumentNullException.ThrowIfNull(config);
- ArgumentNullException.ThrowIfNull(activity);
-
- var rootView = activity.Window!.DecorView.RootView;
- var snackbar = Snackbar.Make(
- activity,
- rootView!,
- config.Message,
- Convert.ToInt32(config.Duration.TotalMilliseconds)
- );
+ if (_config.Duration.TotalMilliseconds <= 0
+ || Looper.MainLooper == null)
+ return;
- if (snackbar.View is Snackbar.SnackbarLayout snackbarView &&
- snackbarView.GetChildAt(0) is SnackbarContentLayout snackbarContent)
+ new Handler(Looper.MainLooper).PostDelayed(() =>
{
- var insets = rootView!.GetInsets();
- snackbarView
- .SetRoundedBackground(config.BackgroundColor, config.CornerRadius)
- .SetMargin(config.Margin, insets)
- .SetPadding(config.Padding)
- .SetGravity(config.Position);
-
- _textView = ConfigureText(snackbar, snackbarContent, config.FontSize, config.TextColor);
-
- if (config.Action is not null)
- {
- _actionView = ConfigureAction(activity, snackbar, snackbarContent, config.Action);
- }
- if (config.LeadingIcon is not null)
- {
- _leadingIconView = snackbarContent.AddIcon(activity, config.LeadingIcon, 0);
- }
- if (config.TrailingIcon is not null)
- {
- _trailingIconView = snackbarContent.AddIcon(activity, config.TrailingIcon, snackbarContent.ChildCount);
- }
-
- _textView!.SetMargin(new Thickness(_leadingIconView is not null ? config.Spacing : 0,0,0,0));
- _actionView?.SetMargin(new Thickness(config.Spacing,0,_trailingIconView is not null ? config.Spacing : 0,0));
-
- snackbarView.SetVisibility(false);
- }
-
- snackbar.SetTextMaxLines(TextMaxLines);
- return snackbar;
+ Dismiss();
+ }, (long)_config.Duration.TotalMilliseconds);
}
-
- private static TextView? ConfigureText(Snackbar snackbar, SnackbarContentLayout contentLayout, double fontSize, Color textColor)
+
+ public void Dismiss() => _dialog?.Dismiss();
+
+ public void Dispose() => _dialog?.Dispose();
+
+ private static TextView ConfigureText(Activity activity, MaterialSnackbarConfig config)
{
- snackbar.SetTextColor(textColor.ToInt());
- if (contentLayout.MessageView is not {} textView) return null;
+ var textView = new TextView(activity)
+ {
+ Id = Android.Views.View.GenerateViewId(),
+ LayoutParameters = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WrapContent,
+ ViewGroup.LayoutParams.WrapContent)
+ {
+ Gravity = GravityFlags.CenterVertical
+ }
+ };
+ textView.SetText(config.Message, TextDecorations.None, config.CharacterSpacing);
+ textView.SetTextColor(config.TextColor.ToPlatform());
+ textView.SetTextSize(Android.Util.ComplexUnitType.Dip, (float)config.FontSize);
+ textView.SetLineBreakMode(config.LineBreakMode, TextMaxLines);
textView.SetBackgroundColor(Android.Graphics.Color.Transparent);
- //textView.SetTypeface(actionButton.Typeface, TypefaceStyle.Normal);
- textView.SetTextSize(Android.Util.ComplexUnitType.Dip, (float)fontSize);
- textView.Ellipsize = TextUtils.TruncateAt.End;
textView.SetIncludeFontPadding(false);
-
+ textView.SetFontAttributes(config.FontAttributes);
+
return textView;
}
-
- private static Button? ConfigureAction(Activity activity, Snackbar snackbar, SnackbarContentLayout contentLayout, MaterialSnackbarConfig.ActionConfig config)
+
+ private static Button ConfigureAction(Activity activity, MaterialSnackbarConfig.ActionConfig config, Action dismiss)
{
- var text = new SpannableString(config.Text);
-
- CharacterStyle textStyle = config.TextDecorations switch
+ var actionButton = new Button(activity)
{
- TextDecorations.Underline => new UnderlineSpan(),
- TextDecorations.Strikethrough => new StrikethroughSpan(),
- _ => new LetterSpacingSpan(0)
+ Id = View.GenerateViewId(),
+ LayoutParameters = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WrapContent,
+ ViewGroup.LayoutParams.WrapContent)
+ {
+ Gravity = GravityFlags.CenterVertical
+ }
};
- text.SetSpan(textStyle, 0, config.Text.Length, SpanTypes.ExclusiveExclusive);
-
- snackbar.SetActionTextColor(config.Color.ToInt());
- snackbar.SetAction(text, v => config.Action.Invoke());
-
- if (contentLayout.ActionView is not {} actionButton) return null;
+ actionButton.SetText(config.Text, config.TextDecorations);
+ actionButton.SetTextColor(config.Color.ToPlatform());
actionButton.SetBackgroundColor(Colors.Transparent.ToPlatform());
actionButton.SetTypeface(actionButton.Typeface, TypefaceStyle.Bold);
- //var mediumTypeface = Typeface.CreateFromAsset(activity.Assets, MaterialFontFamily.Medium);
actionButton.SetTextSize(Android.Util.ComplexUnitType.Dip, (float)config.FontSize);
actionButton.Ellipsize = TextUtils.TruncateAt.Middle;
actionButton.SetPadding(0);
actionButton.SetIncludeFontPadding(false);
+ actionButton.SetAllCaps(false);
+ actionButton.Click += (s, e) =>
+ {
+ config.Action.Invoke();
+ dismiss.Invoke();
+ };
return actionButton;
}
}
-
-class LetterSpacingSpan(float letterSpacing) : MetricAffectingSpan
-{
- public float LetterSpacing => letterSpacing;
-
- public override void UpdateDrawState(TextPaint? ds) => Apply(ds);
-
- public override void UpdateMeasureState(TextPaint paint) => Apply(paint);
-
- private void Apply(TextPaint? paint)
- {
- if (paint is null) return;
- paint.LetterSpacing = LetterSpacing;
- }
-}
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.MaciOS.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.MaciOS.cs
index 06478d07..c9387246 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.MaciOS.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarBuilder.MaciOS.cs
@@ -1,4 +1,5 @@
using Foundation;
+using HorusStudio.Maui.MaterialDesignControls.Extensions.Label;
using Microsoft.Maui.Platform;
using UIKit;
@@ -8,6 +9,8 @@ class MaterialSnackbarBuilder : UIView
{
#region Constants
+ private const int TextMaxLines = 20;
+
private const bool UseBlur = false;
private const bool UseAnimation = true;
private const UIBlurEffectStyle BlurEffectStyle = UIBlurEffectStyle.Dark;
@@ -272,14 +275,17 @@ private static UIView ConfigureText(MaterialSnackbarConfig materialSnackbarConfi
var label = new UILabel
{
- Text = materialSnackbarConfig.Message,
TextColor = materialSnackbarConfig.TextColor.ToPlatform(),
Font = font,
LineBreakMode = UILineBreakMode.WordWrap,
Lines = 0,
TranslatesAutoresizingMaskIntoConstraints = false
};
-
+
+ label.SetTextCharacterSpacing(materialSnackbarConfig.Message, materialSnackbarConfig.CharacterSpacing);
+ label.SetLineBreakMode(materialSnackbarConfig.LineBreakMode, TextMaxLines);
+ label.SetFontAttributes(materialSnackbarConfig.FontAttributes);
+
return label;
}
}
\ No newline at end of file
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarConfig.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarConfig.cs
index 20959db4..bfa18306 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarConfig.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Snackbar/MaterialSnackbarConfig.cs
@@ -48,6 +48,9 @@ public class MaterialSnackbarConfig(string message)
private static readonly Func DefaultBackgroundColorBuilder = () => new AppThemeBindingExtension { Light = MaterialLightTheme.InverseSurface, Dark = MaterialDarkTheme.InverseSurface }.GetValueForCurrentTheme();
private static readonly Func DefaultTextColorBuilder = () => new AppThemeBindingExtension { Light = MaterialLightTheme.InverseOnSurface, Dark = MaterialDarkTheme.InverseOnSurface }.GetValueForCurrentTheme();
private static readonly Func DefaultFontSizeBuilder = () => MaterialFontSize.BodyMedium;
+ private static readonly Func DefaultCharacterSpacingBuilder = () => 0;
+ private static readonly Func DefaultLineBreakModeBuilder = () => LineBreakMode.WordWrap;
+ private static readonly Func DefaultFontAttributesBuilder = () => FontAttributes.None;
private static readonly Func DefaultActionColorBuilder = () => new AppThemeBindingExtension { Light = MaterialLightTheme.InversePrimary, Dark = MaterialDarkTheme.InversePrimary }.GetValueForCurrentTheme();
private static readonly Func DefaultActionSizeBuilder = () => MaterialFontSize.BodyMedium;
private static readonly Func DefaultIconColorBuilder = () => new AppThemeBindingExtension { Light = MaterialLightTheme.InverseOnSurface, Dark = MaterialDarkTheme.InverseOnSurface }.GetValueForCurrentTheme();
@@ -55,6 +58,9 @@ public class MaterialSnackbarConfig(string message)
private static Color? _defaultBackgroundColor;
private static Color? _defaultTextColor;
private static double? _defaultFontSize;
+ private static LineBreakMode? _defaultLineBreakMode;
+ private static FontAttributes? _defaultFontAttributes;
+ private static double? _defaultCharacterSpacing;
private static Color? _defaultActionColor;
private static double? _defaultActionSize;
private static Color? _defaultIconColor;
@@ -125,6 +131,43 @@ public static double DefaultFontSize
set => _defaultFontSize = value;
}
+ ///
+ /// Text character spacing to be applied by default to every Snackbar that doesn't set one.
+ ///
+ ///
+ /// 0
+ ///
+ public static double DefaultCharacterSpacing
+ {
+ get => _defaultCharacterSpacing ?? DefaultCharacterSpacingBuilder();
+ set => _defaultCharacterSpacing = value;
+ }
+
+ ///
+ /// Determines how Text is shown by default when the length is overflowing the size of this snackbar.
+ ///
+ ///
+ /// LineBreakMode.WordWrap
+ ///
+ public static LineBreakMode DefaultLineBreakMode
+ {
+ get => _defaultLineBreakMode ?? DefaultLineBreakModeBuilder();
+ set => _defaultLineBreakMode = value;
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether the font for the text of this snackbar is bold, italic, or neither.
+ /// This is a bindable property.
+ ///
+ /// ///
+ /// FontAttributes.None
+ ///
+ public static FontAttributes DefaultFontAttributes
+ {
+ get => _defaultFontAttributes ?? DefaultFontAttributesBuilder();
+ set => _defaultFontAttributes = value;
+ }
+
///
/// Action text color to be applied by default to every Snackbar that doesn't set one.
///
@@ -188,6 +231,9 @@ public static Color DefaultIconColor
private Color? _backgroundColor;
private float? _cornerRadius;
private double? _fontSize;
+ private double? _characterSpacing;
+ private LineBreakMode? _lineBreakMode;
+ private FontAttributes? _fontAttributes;
private Color? _textColor;
private Thickness? _margin;
private Thickness? _padding;
@@ -238,6 +284,43 @@ public double FontSize
set => _fontSize = value;
}
+ ///
+ /// Gets or sets character spacing for snackbar.
+ ///
+ ///
+ /// 0
+ ///
+ public double CharacterSpacing
+ {
+ get => _characterSpacing ?? DefaultCharacterSpacing;
+ set => _characterSpacing = value;
+ }
+
+ ///
+ /// Determines how Text is shown when the length is overflowing the size of this snackbar.
+ ///
+ ///
+ /// LineBreakMode.WordWrap
+ ///
+ public LineBreakMode LineBreakMode
+ {
+ get => _lineBreakMode ?? DefaultLineBreakMode;
+ set => _lineBreakMode = value;
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether the font for the text of this snackbar is bold, italic, or neither.
+ /// This is a bindable property.
+ ///
+ /// ///
+ /// FontAttributes.None
+ ///
+ public FontAttributes FontAttributes
+ {
+ get => _fontAttributes ?? DefaultFontAttributes;
+ set => _fontAttributes = value;
+ }
+
///
/// Gets or sets a color for text on snackbar.
///
@@ -351,6 +434,9 @@ internal static void Configure(MaterialSnackbarOptions options)
if (options.DefaultActionTextDecorations != null) DefaultActionTextDecorations = options.DefaultActionTextDecorations.Value;
if (options.DefaultCornerRadius != null) DefaultCornerRadius = options.DefaultCornerRadius.Value;
if (options.DefaultFontSize != null) DefaultFontSize = options.DefaultFontSize.Value;
+ if (options.DefaultCharacterSpacing != null) DefaultCharacterSpacing = options.DefaultCharacterSpacing.Value;
+ if (options.DefaultLineBreakMode != null) DefaultLineBreakMode = options.DefaultLineBreakMode.Value;
+ if (options.DefaultFontAttributes != null) DefaultFontAttributes = options.DefaultFontAttributes.Value;
if (options.DefaultIconSize != null) DefaultIconSize = options.DefaultIconSize.Value;
}
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Switch/MaterialSwitch.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Switch/MaterialSwitch.cs
index 1833c389..a8148641 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/Switch/MaterialSwitch.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/Switch/MaterialSwitch.cs
@@ -585,7 +585,15 @@ public ITouchAnimation TouchAnimation
#region Events
+ ///
+ /// Occurs when the switch is toggled.
+ ///
public event EventHandler? Toggled;
+
+ ///
+ /// Occurs when the switch is touched.
+ ///
+ public event EventHandler? Touch;
#endregion Events
@@ -1069,6 +1077,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+ Touch?.Invoke(this, new TouchEventArgs(gestureType));
+
switch (gestureType)
{
case TouchEventType.Pressed:
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Controls/ViewButton/MaterialViewButton.cs b/src/HorusStudio.Maui.MaterialDesignControls/Controls/ViewButton/MaterialViewButton.cs
index d77a8aeb..4dce1645 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Controls/ViewButton/MaterialViewButton.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Controls/ViewButton/MaterialViewButton.cs
@@ -107,6 +107,7 @@ public ITouchAnimation TouchAnimation
private EventHandler? _clicked;
private EventHandler? _pressed;
private EventHandler? _released;
+ private EventHandler? _touch;
private readonly object _objectLock = new();
///
@@ -177,6 +178,30 @@ public event EventHandler Released
}
}
}
+
+ ///
+ /// Occurs when the card is touched.
+ ///
+ public event EventHandler? Touch
+ {
+ add
+ {
+ lock (_objectLock)
+ {
+ _touch += value;
+ UpdateTouchBehavior();
+ }
+ }
+ remove
+ {
+ lock (_objectLock)
+ {
+ _touch -= value;
+ UpdateTouchBehavior();
+ }
+ }
+ }
+
#endregion Events
@@ -189,6 +214,8 @@ public async void OnTouch(TouchEventType gestureType)
if (!IsEnabled) return;
await TouchAnimationManager.AnimateAsync(this, gestureType);
+ _touch?.Invoke(this, new TouchEventArgs(gestureType));
+
switch (gestureType)
{
case TouchEventType.Pressed:
@@ -221,7 +248,7 @@ private void UpdateTouchBehavior()
{
var touchBehavior = Behaviors.FirstOrDefault(b => b is TouchBehavior) as TouchBehavior;
- if (Command != null || _clicked != null || _pressed != null || _released != null)
+ if (Command != null || _clicked != null || _pressed != null || _released != null || _touch != null)
{
if (touchBehavior == null)
{
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.Android.cs b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.Android.cs
new file mode 100644
index 00000000..480e8bfa
--- /dev/null
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.Android.cs
@@ -0,0 +1,94 @@
+using Android.Text;
+using Android.Text.Style;
+using Android.Widget;
+using Button = Android.Widget.Button;
+
+namespace HorusStudio.Maui.MaterialDesignControls.Extensions.Label;
+
+public static partial class LabelExtensions
+{
+ public static void SetText(this TextView textView, string text, TextDecorations textDecorations, double characterSpacing)
+ {
+ var spannableString = GetSpannableString(text, textDecorations, characterSpacing);
+ textView.LetterSpacing = (float)characterSpacing;
+ textView.SetText(spannableString, TextView.BufferType.Spannable);
+ }
+
+ public static void SetText(this Button button, string text, TextDecorations textDecorations)
+ {
+ var spannableString = GetSpannableString(text, textDecorations, 0);
+ button.SetText(spannableString, TextView.BufferType.Spannable);
+ }
+
+ private static SpannableString GetSpannableString(string text, TextDecorations textDecorations, double characterSpacing)
+ {
+ var spannableString = new SpannableString(text);
+ CharacterStyle textStyle = textDecorations switch
+ {
+ TextDecorations.Underline => new UnderlineSpan(),
+ TextDecorations.Strikethrough => new StrikethroughSpan(),
+ _ => new LetterSpacingSpan((float)characterSpacing)
+ };
+ spannableString.SetSpan(textStyle, 0, text.Length, SpanTypes.ExclusiveExclusive);
+ return spannableString;
+ }
+
+ public static void SetLineBreakMode(this TextView textView, LineBreakMode lineBreakMode, int textMaxLines)
+ {
+ switch (lineBreakMode)
+ {
+ case LineBreakMode.NoWrap:
+ case LineBreakMode.TailTruncation:
+ textView.SetSingleLine(true);
+ textView.Ellipsize = Android.Text.TextUtils.TruncateAt.End;
+ break;
+ case LineBreakMode.HeadTruncation:
+ textView.SetSingleLine(true);
+ textView.Ellipsize = Android.Text.TextUtils.TruncateAt.Start;
+ break;
+ case LineBreakMode.MiddleTruncation:
+ textView.SetSingleLine(true);
+ textView.Ellipsize = Android.Text.TextUtils.TruncateAt.Middle;
+ break;
+ case LineBreakMode.WordWrap:
+ case LineBreakMode.CharacterWrap:
+ textView.Ellipsize = null;
+ textView.SetMaxLines(textMaxLines);
+ break;
+ }
+ }
+
+ public static void SetFontAttributes(this TextView textView, FontAttributes fontAttributes)
+ {
+ switch (fontAttributes)
+ {
+ case FontAttributes.Bold:
+ textView.SetTypeface(textView.Typeface, Android.Graphics.TypefaceStyle.Bold);
+ break;
+ case FontAttributes.Italic:
+ textView.SetTypeface(textView.Typeface, Android.Graphics.TypefaceStyle.Italic);
+ break;
+ case FontAttributes.Bold | FontAttributes.Italic:
+ textView.SetTypeface(textView.Typeface, Android.Graphics.TypefaceStyle.BoldItalic);
+ break;
+ default:
+ textView.SetTypeface(textView.Typeface, Android.Graphics.TypefaceStyle.Normal);
+ break;
+ }
+ }
+}
+
+class LetterSpacingSpan(float letterSpacing) : MetricAffectingSpan
+{
+ public float LetterSpacing => letterSpacing;
+
+ public override void UpdateDrawState(TextPaint? ds) => Apply(ds);
+
+ public override void UpdateMeasureState(TextPaint paint) => Apply(paint);
+
+ private void Apply(TextPaint? paint)
+ {
+ if (paint is null) return;
+ paint.LetterSpacing = LetterSpacing;
+ }
+}
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.MaciOS.cs b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.MaciOS.cs
new file mode 100644
index 00000000..02690f1f
--- /dev/null
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Label/LabelExtensions.MaciOS.cs
@@ -0,0 +1,69 @@
+using Foundation;
+using UIKit;
+
+namespace HorusStudio.Maui.MaterialDesignControls.Extensions.Label;
+
+public static partial class LabelExtensions
+{
+ public static void SetTextCharacterSpacing(this UILabel uiLabel, string text, double characterSpacing)
+ {
+ var attributedString = new NSMutableAttributedString(text);
+ attributedString.AddAttribute(
+ UIStringAttributeKey.KerningAdjustment,
+ NSNumber.FromDouble(characterSpacing),
+ new NSRange(0, text.Length)
+ );
+
+ uiLabel.AttributedText = attributedString;
+ }
+
+ public static void SetLineBreakMode(this UILabel uiLabel, LineBreakMode lineBreakMode, int textMaxLines)
+ {
+ switch (lineBreakMode)
+ {
+ case LineBreakMode.NoWrap:
+ case LineBreakMode.TailTruncation:
+ uiLabel.LineBreakMode = UILineBreakMode.TailTruncation;
+ uiLabel.Lines = 1;
+ break;
+ case LineBreakMode.HeadTruncation:
+ uiLabel.LineBreakMode = UILineBreakMode.HeadTruncation;
+ uiLabel.Lines = 1;
+ break;
+ case LineBreakMode.MiddleTruncation:
+ uiLabel.LineBreakMode = UILineBreakMode.MiddleTruncation;
+ uiLabel.Lines = 1;
+ break;
+ case LineBreakMode.WordWrap:
+ uiLabel.LineBreakMode = UILineBreakMode.WordWrap;
+ uiLabel.Lines = textMaxLines;
+ break;
+ case LineBreakMode.CharacterWrap:
+ uiLabel.LineBreakMode = UILineBreakMode.CharacterWrap;
+ uiLabel.Lines = textMaxLines;
+ break;
+ }
+ }
+
+ public static void SetFontAttributes(this UILabel label, FontAttributes fontAttributes)
+ {
+ var currentFont = label.Font ?? UIFont.SystemFontOfSize(UIFont.LabelFontSize);
+ switch (fontAttributes)
+ {
+ case FontAttributes.Bold:
+ label.Font = UIFont.BoldSystemFontOfSize(currentFont.PointSize);
+ break;
+ case FontAttributes.Italic:
+ label.Font = UIFont.ItalicSystemFontOfSize(currentFont.PointSize);
+ break;
+ case FontAttributes.Bold | FontAttributes.Italic:
+ var descriptor = currentFont.FontDescriptor
+ .CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic);
+ label.Font = UIFont.FromDescriptor(descriptor, currentFont.PointSize);
+ break;
+ default:
+ label.Font = UIFont.SystemFontOfSize(currentFont.PointSize);
+ break;
+ }
+ }
+}
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Snackbar/SnackbarExtensions.Android.cs b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Snackbar/SnackbarExtensions.Android.cs
index f47033c5..a1493d5b 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Snackbar/SnackbarExtensions.Android.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/Snackbar/SnackbarExtensions.Android.cs
@@ -1,8 +1,10 @@
using Android.App;
using Android.Views;
+using Android.Widget;
using HorusStudio.Maui.MaterialDesignControls;
using ImageButton = Android.Widget.ImageButton;
using Microsoft.Maui.Platform;
+using View = Android.Views.View;
namespace Google.Android.Material.Snackbar;
@@ -18,7 +20,17 @@ public static class SnackbarExtensions
var icon = source.ToDrawable(size, color);
if (icon is null) return null;
- var button = new ImageButton(activity);
+ var button = new ImageButton(activity)
+ {
+ Id = View.GenerateViewId(),
+ LayoutParameters = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WrapContent,
+ ViewGroup.LayoutParams.WrapContent)
+ {
+ Gravity = GravityFlags.CenterVertical
+ }
+ };
+
button.SetImageDrawable(icon);
button.SetPadding(padding);
button.SetBackgroundColor(Colors.Transparent.ToPlatform());
@@ -29,7 +41,7 @@ public static class SnackbarExtensions
return button;
}
-
+
public static ImageButton? AddIcon(this SnackbarContentLayout contentLayout,
Activity activity,
ImageSource source,
@@ -49,18 +61,4 @@ public static class SnackbarExtensions
return iconView;
}
-
- public static ImageButton? AddIcon(this SnackbarContentLayout contentLayout,
- Activity activity,
- MaterialSnackbarConfig.IconConfig config,
- int index)
- {
- var iconView = AddIcon(contentLayout, activity, config.Source, config.Size, config.Color, new Thickness(0), config.Action, index);
- if (iconView?.LayoutParameters != null)
- {
- iconView.LayoutParameters.Width = config.Size.DpToPixels();
- iconView.LayoutParameters.Height = ViewGroup.LayoutParams.MatchParent;
- }
- return iconView;
- }
}
\ No newline at end of file
diff --git a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/VisualElement/ViewExtensions.Android.cs b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/VisualElement/ViewExtensions.Android.cs
index f46fd294..029153a4 100644
--- a/src/HorusStudio.Maui.MaterialDesignControls/Extensions/VisualElement/ViewExtensions.Android.cs
+++ b/src/HorusStudio.Maui.MaterialDesignControls/Extensions/VisualElement/ViewExtensions.Android.cs
@@ -76,7 +76,7 @@ public static View SetGravity(this View view, MaterialFloatingButtonPosition pos
return view;
}
- public static View SetGravity(this View view, MaterialSnackbarPosition position)
+ public static void SetGravity(this Window window, MaterialSnackbarPosition position)
{
var gravityFlags = position switch
{
@@ -85,19 +85,8 @@ public static View SetGravity(this View view, MaterialSnackbarPosition position)
_ => throw new ArgumentOutOfRangeException(nameof(position), position, "Snackbar position value is not valid.")
};
- switch (view.LayoutParameters)
- {
- case FrameLayout.LayoutParams frameLayoutParams:
- frameLayoutParams.Gravity = gravityFlags;
- view.LayoutParameters = frameLayoutParams;
- break;
- case LinearLayout.LayoutParams linearLayoutParams:
- linearLayoutParams.Gravity = gravityFlags;
- view.LayoutParameters = linearLayoutParams;
- break;
- }
-
- return view;
+ window.SetGravity(gravityFlags);
+ window?.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
}
public static View SetGravity(this View view, LayoutOptions horizontalLayoutOptions, LayoutOptions verticalLayoutOptions)
diff --git a/tools/XMLDoc2Markdown.zip b/tools/XMLDoc2Markdown.zip
new file mode 100644
index 00000000..7633f735
Binary files /dev/null and b/tools/XMLDoc2Markdown.zip differ