diff --git a/maui/src/AssemblyInfo.cs b/maui/src/AssemblyInfo.cs
index 9864bff0..2652952b 100644
--- a/maui/src/AssemblyInfo.cs
+++ b/maui/src/AssemblyInfo.cs
@@ -2,12 +2,15 @@
[assembly: InternalsVisibleTo("Syncfusion.Maui.Toolkit.UnitTest")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.BottomSheet")]
+[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Buttons")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Calendar")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Carousel")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Charts")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Chips")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.EffectsView")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.NavigationDrawer")]
+[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.NumericEntry")]
+[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.NumericUpDown")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.PullToRefresh")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.SegmentedControl")]
[assembly: XmlnsDefinition("http://schemas.syncfusion.com/maui/toolkit", "Syncfusion.Maui.Toolkit.Shimmer")]
diff --git a/maui/src/Core/EntryView/SfEntryView.cs b/maui/src/Core/EntryView/SfEntryView.cs
new file mode 100644
index 00000000..8f122d71
--- /dev/null
+++ b/maui/src/Core/EntryView/SfEntryView.cs
@@ -0,0 +1,190 @@
+using Color = Microsoft.Maui.Graphics.Color;
+#if WINDOWS
+using WColor = Windows.UI.Color;
+using GradientBrush = Microsoft.UI.Xaml.Media.GradientBrush;
+#elif ANDROID
+using Android.Content;
+using Android.OS;
+using Android.Util;
+#endif
+
+namespace Syncfusion.Maui.Toolkit.EntryView
+{
+ ///
+ /// The SfEntryView is a entry component which is used to create input view entry for NumericEntry.
+ ///
+ internal partial class SfEntryView : Entry
+ {
+ #region Fields
+
+ IDrawable? _drawable;
+
+#if MACCATALYST || IOS
+ Color? _focusedColor = Color.FromArgb("#8EBDFF");
+#else
+ Color? _focusedColor = Colors.Gray;
+#endif
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfEntryView()
+ {
+ Initialize();
+ }
+
+ void Initialize()
+ {
+ Style = new Style(typeof(SfEntryView));
+ BackgroundColor = Colors.Transparent;
+ TextColor = Colors.Black;
+ FontSize = 14d;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value for focused stroke.
+ ///
+ internal Color? FocusedStroke
+ {
+ get
+ {
+ return _focusedColor;
+ }
+ set
+ {
+ _focusedColor = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the drawable value.
+ ///
+ internal IDrawable? Drawable
+ {
+ get { return _drawable; }
+ set { _drawable = value; }
+ }
+
+
+ ///
+ /// Gets or sets the button size value.
+ ///
+ internal double ButtonSize { get; set; }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Handler changed method.
+ ///
+ protected override void OnHandlerChanged()
+ {
+ base.OnHandlerChanged();
+ // Unsubscribe from events to avoid duplicate handlers
+ Focused -= SfEntry_Focused;
+ Unfocused -= SfEntry_Unfocused;
+
+ // Check if the Handler is available for attaching event handlers
+ if (Handler != null)
+ {
+ #if WINDOWS
+ if (Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox textbox)
+ {
+ AdjustWindowsTextBoxStyles(textbox);
+ }
+ #elif ANDROID
+ if (Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText textbox)
+ {
+ AdjustAndroidTextBoxStyles(textbox);
+ }
+ #endif
+ // Subscribe to events when the handler is set
+ Focused += SfEntry_Focused;
+ Unfocused += SfEntry_Unfocused;
+ }
+ }
+
+ #if WINDOWS
+ private void AdjustWindowsTextBoxStyles(Microsoft.UI.Xaml.Controls.TextBox textbox)
+ {
+ textbox.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
+ textbox.Resources["TextControlBorderThemeThicknessFocused"] = textbox.BorderThickness;
+
+ if (textbox.Resources["TextControlBorderBrushFocused"] is GradientBrush brush &&
+ brush.GradientStops.FirstOrDefault()?.Color is WColor color)
+ {
+ FocusedStroke = new Color(color.R, color.G, color.B, color.A);
+ }
+ }
+ #endif
+
+ #if ANDROID
+ private void AdjustAndroidTextBoxStyles(AndroidX.AppCompat.Widget.AppCompatEditText textbox)
+ {
+ textbox.SetBackgroundColor(Android.Graphics.Color.Transparent);
+ var themeAccentColor = textbox.Context != null ? GetThemeAccentColor(textbox.Context) : Android.Graphics.Color.Transparent.ToArgb();
+ var color = new Android.Graphics.Color(themeAccentColor);
+ FocusedStroke = new Color(color.R, color.G, color.B, color.A);
+ }
+ #endif
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// It invoked when entry get unfocused.
+ ///
+ /// The sender.
+ /// The focus event args.
+ void SfEntry_Unfocused(object? sender, FocusEventArgs e)
+ {
+ if (_drawable is SfView sfView)
+ {
+ sfView.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// It invoked when the entry get focus.
+ ///
+ /// The sender.
+ /// The focus event args.
+ void SfEntry_Focused(object? sender, FocusEventArgs e)
+ {
+ if (_drawable is SfView sfView)
+ {
+ sfView.InvalidateDrawable();
+ }
+ }
+
+#if ANDROID
+ ///
+ /// Get theme accent color method.
+ ///
+ /// The context.
+ static int GetThemeAccentColor(Context context)
+ {
+ int colorAttr = Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop
+ ? Android.Resource.Attribute.ColorAccent
+ : context.Resources?.GetIdentifier("colorAccent", "attr", context.PackageName)
+ ?? Android.Resource.Color.BackgroundDark;
+
+ TypedValue outValue = new();
+ bool resolved = context.Theme?.ResolveAttribute(colorAttr, outValue, true) ?? false;
+
+ return resolved ? outValue.Data : Android.Resource.Color.BackgroundDark;
+ }
+#endif
+ #endregion
+ }
+}
diff --git a/maui/src/Core/Theme/Resources/DefaultTheme.xaml b/maui/src/Core/Theme/Resources/DefaultTheme.xaml
index e1b56c47..b53a15a3 100644
--- a/maui/src/Core/Theme/Resources/DefaultTheme.xaml
+++ b/maui/src/Core/Theme/Resources/DefaultTheme.xaml
@@ -310,6 +310,52 @@
12
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16
+
CommonTheme
diff --git a/maui/src/NumericEntry/DropdownEntryRenderer.cs b/maui/src/NumericEntry/DropdownEntryRenderer.cs
new file mode 100644
index 00000000..86f36f40
--- /dev/null
+++ b/maui/src/NumericEntry/DropdownEntryRenderer.cs
@@ -0,0 +1,469 @@
+namespace Syncfusion.Maui.Toolkit.EntryRenderer
+{
+ ///
+ /// The FluentSfEntryRenderer class is responsible for rendering the entry
+ /// with a Fluent design style. It implements the IUpDownButtonRenderer interface, providing
+ /// methods to render the dropdown, set its options, and specify the selected option.
+ ///
+ internal class FluentSfEntryRenderer : IUpDownButtonRenderer
+ {
+ ///
+ /// Padding value for circles.
+ ///
+ internal float _padding = 13f;
+
+ ///
+ /// Padding value specifically for circles.
+ ///
+ internal float _circlePadding = 2f;
+
+ ///
+ /// Draws the border.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the border's bounds.
+ /// Indicates if the dropdown is focused.
+ /// The color of the border.
+ /// The color of the border when focused.
+ public void DrawBorder(ICanvas canvas, RectF rectF, bool isFocused, Color borderColor, Color focusedBorderColor)
+ {
+ canvas.StrokeColor = Color.FromRgba(240, 240, 240, 255);
+ canvas.StrokeSize = 2;
+ canvas.DrawRoundedRectangle(rectF, 5);
+ canvas.ResetStroke();
+
+ if (isFocused)
+ {
+ canvas.StrokeColor = focusedBorderColor;
+ canvas.DrawLine(rectF.X + 2, rectF.Y + rectF.Height - 1.5f, rectF.X + rectF.Width - 2, rectF.Height - 1.5f);
+ }
+ else
+ {
+ canvas.StrokeColor = borderColor;
+ canvas.DrawLine(rectF.X + 2, rectF.Y + rectF.Height - 1, rectF.X + rectF.Width - 2, rectF.Height - 1);
+ }
+ canvas.StrokeSize = 0.6f;
+ canvas.DrawLine(rectF.X + 3, rectF.Y + rectF.Height - 0.6f, rectF.X + rectF.Width - 3, rectF.Height - 0.6f);
+ canvas.DrawLine(rectF.X + 4, rectF.Y + rectF.Height, rectF.X + rectF.Width - 4, rectF.Height);
+
+ canvas.StrokeSize = 1f;
+ }
+
+ ///
+ /// Draws the clear button.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawClearButton(ICanvas canvas, RectF rectF)
+ {
+ PointF A = new Point(0, 0);
+ PointF B = new Point(0, 0);
+ PointF C = new Point(0, 0);
+ PointF D = new Point(0, 0);
+
+ A.X = rectF.X + _padding;
+ A.Y = rectF.Y + _padding;
+
+ B.X = rectF.X + rectF.Width - _padding;
+ B.Y = rectF.Y + rectF.Height - _padding;
+
+ canvas.DrawLine(A, B);
+
+ C.X = rectF.X + _padding;
+ C.Y = rectF.Y + rectF.Height - _padding;
+
+ D.X = rectF.X + rectF.Width - _padding;
+ D.Y = rectF.Y + _padding;
+
+ canvas.DrawLine(C, D);
+ canvas.DrawCircle((A.X + D.X) / 2, (B.Y + D.Y) / 2, (float)Math.Ceiling((Math.Sqrt(2d) * (D.X - A.X)) / 2f) + _circlePadding);
+ }
+
+ ///
+ /// Draws the down button
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawDownButton(ICanvas canvas, RectF rectF)
+ {
+ var imageWidth = (rectF.Width / 2) - (_padding / 2);
+ var imageHeight = (rectF.Height / 2) - (_padding / 2);
+
+ rectF.X = rectF.Center.X - imageWidth / 2;
+ rectF.Y = rectF.Center.Y - imageHeight / 4;
+ rectF.Width = imageWidth;
+ rectF.Height = imageHeight / 2;
+
+ float x = rectF.X;
+ float y = rectF.Y;
+ float width = x + rectF.Width;
+ float height = y + rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, y);
+ path.LineTo(midWidth, height);
+ path.LineTo(width, y);
+ path.LineTo(x, y);
+ path.Close();
+ canvas.FillPath(path);
+
+ }
+
+ ///
+ /// Draws the up button of the dropdown.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawUpButton(ICanvas canvas, RectF rectF) {
+ var imageSize = (rectF.Width / 2) - (_padding / 2);
+ rectF.X = rectF.Center.X - imageSize / 2;
+ rectF.Y = rectF.Center.Y - imageSize / 4;
+ rectF.Width = imageSize;
+ rectF.Height = imageSize / 2;
+
+ float x = rectF.X;
+ float y = rectF.Y;
+ float width = x + rectF.Width;
+ float height = y + rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, height);
+ path.LineTo(midWidth, y);
+ path.LineTo(width, height);
+ path.LineTo(x, height);
+ path.Close();
+ canvas.FillPath(path);
+ }
+ }
+
+ ///
+ /// The CupertinoSfEntryRenderer class is responsible for rendering the entry
+ /// with a Cupertino design style. It implements the IUpDownButtonRenderer interface, providing
+ /// methods to render the dropdown, set its options, and specify the selected option.
+ ///
+ internal class CupertinoSfEntryRenderer : IUpDownButtonRenderer
+ {
+ ///
+ /// General padding value between the elements.
+ ///
+ internal float _padding = 12;
+
+ ///
+ /// Padding value specifically for the clear button.
+ ///
+ internal float _clearButtonPadding = 12;
+
+ ///
+ /// Padding value for circular elements.
+ ///
+ internal float _circlePadding = 2;
+
+ ///
+ /// Size of the border stroke, set to zero by default.
+ ///
+ internal float _borderStrokeSize = 0;
+
+ ///
+ /// Draws the border.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the border's bounds.
+ /// Indicates if the dropdown is focused.
+ /// The color of the border.
+ /// The color of the border when focused.
+ public void DrawBorder(ICanvas canvas, RectF rectF, bool isFocused, Color borderColor, Color focusedBorderColor)
+ {
+ if (!isFocused)
+ {
+ canvas.StrokeColor = borderColor;
+ canvas.StrokeSize = 1;
+ }
+ else
+ {
+#if MACCATALYST
+ canvas.StrokeColor = focusedBorderColor;
+ canvas.StrokeSize = _borderStrokeSize == 0 ? 6 : _borderStrokeSize;
+#else
+ canvas.StrokeColor = borderColor;
+ canvas.StrokeSize = 1;
+#endif
+ }
+
+ canvas.SaveState();
+ canvas.DrawRoundedRectangle(rectF, 6);
+ canvas.ResetStroke();
+ }
+
+ ///
+ /// Draws the clear button.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawClearButton(ICanvas canvas, RectF rectF)
+ {
+ PointF A = new Point(0, 0);
+ PointF B = new Point(0, 0);
+ PointF C = new Point(0, 0);
+ PointF D = new Point(0, 0);
+
+ A.X = rectF.X + _clearButtonPadding;
+ A.Y = rectF.Y + _clearButtonPadding;
+
+ B.X = rectF.X + rectF.Width - _clearButtonPadding;
+ B.Y = rectF.Y + rectF.Height - _clearButtonPadding;
+
+ canvas.DrawLine(A, B);
+
+ C.X = rectF.X + _clearButtonPadding;
+ C.Y = rectF.Y + rectF.Height - _clearButtonPadding;
+
+ D.X = rectF.X + rectF.Width - _clearButtonPadding;
+ D.Y = rectF.Y + _clearButtonPadding;
+
+ canvas.DrawLine(C, D);
+ canvas.DrawCircle((A.X + D.X) / 2, (B.Y + D.Y) / 2, (float)Math.Ceiling((Math.Sqrt(2d) * (D.X - A.X)) / 2f) + _circlePadding);
+ }
+
+ ///
+ /// Draws the dropdown button of the dropdown.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawDownButton(ICanvas canvas, RectF rectF)
+ {
+ var imageWidth = (rectF.Width / 2) - (_padding / 2);
+ var imageHeight = (rectF.Height / 2) - (_padding / 2);
+
+ rectF.X = rectF.Center.X - imageWidth / 2;
+ rectF.Y = rectF.Center.Y - imageHeight / 4;
+ rectF.Width = imageWidth;
+ rectF.Height = imageHeight / 2;
+
+ float x = rectF.X;
+ float y = rectF.Y;
+ float width = x + rectF.Width;
+ float height = y + rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, y);
+ path.LineTo(midWidth, height);
+ path.LineTo(width, y);
+ path.LineTo(x, y);
+ path.Close();
+ canvas.FillPath(path);
+ }
+
+ ///
+ /// Draws the up button of the dropdown.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawUpButton(ICanvas canvas, RectF rectF) {
+ var imageSize = (rectF.Width / 2) - (_padding / 2);
+ rectF.X = rectF.Center.X - imageSize / 2;
+ rectF.Y = rectF.Center.Y - imageSize / 4;
+ rectF.Width = imageSize;
+ rectF.Height = imageSize / 2;
+
+ float x = rectF.X;
+ float y = rectF.Y + rectF.Height;
+ float width = x + rectF.Width;
+ float height = y - rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, y);
+ path.LineTo(midWidth, height);
+ path.LineTo(width, y);
+ path.LineTo(x, y);
+ path.Close();
+ canvas.FillPath(path);
+ }
+ }
+
+ ///
+ /// The MaterialSfEntryRenderer class is responsible for rendering the entry
+ /// with a Material design style. It implements the IUpDownButtonRenderer interface, providing
+ /// methods to render the dropdown, set its options, and specify the selected option.
+ ///
+ internal class MaterialSfEntryRenderer : IUpDownButtonRenderer
+ {
+ ///
+ /// General padding value between the elements.
+ ///
+ internal float _padding = 6;
+
+ ///
+ /// Padding value specifically for the clear button.
+ ///
+ internal float _clearButtonPadding = 10;
+
+ ///
+ /// Padding value for circular elements.
+ ///
+ internal float _circlePadding = 2;
+
+ ///
+ /// Draws the border.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the border's bounds.
+ /// Indicates if the dropdown is focused.
+ /// The color of the border.
+ /// The color of the border when focused.
+ public void DrawBorder(ICanvas canvas, RectF rectF, bool isFocused, Color borderColor, Color focusedBorderColor)
+ {
+ if (isFocused)
+ {
+ canvas.StrokeColor = focusedBorderColor;
+ canvas.StrokeSize = 2;
+ }
+ else
+ {
+ canvas.StrokeSize = 1;
+ canvas.StrokeColor = borderColor;
+ }
+
+ canvas.DrawLine(rectF.X + 4, rectF.Y + rectF.Height - 7, rectF.X + rectF.Width - 4, rectF.Height - 7);
+ canvas.StrokeSize = 1;
+
+ }
+
+ ///
+ /// Draws the clear button.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawClearButton(ICanvas canvas, RectF rectF)
+ {
+ PointF A = new Point(0, 0);
+ PointF B = new Point(0, 0);
+ PointF C = new Point(0, 0);
+ PointF D = new Point(0, 0);
+
+ A.X = rectF.X + _clearButtonPadding;
+ A.Y = rectF.Y + _clearButtonPadding;
+
+ B.X = rectF.X + rectF.Width - _clearButtonPadding;
+ B.Y = rectF.Y + rectF.Height - _clearButtonPadding;
+
+ canvas.DrawLine(A, B);
+
+ C.X = rectF.X + _clearButtonPadding;
+ C.Y = rectF.Y + rectF.Height - _clearButtonPadding;
+
+ D.X = rectF.X + rectF.Width - _clearButtonPadding;
+ D.Y = rectF.Y + _clearButtonPadding;
+
+ canvas.DrawLine(C, D);
+ canvas.DrawCircle((A.X + D.X) / 2, (B.Y + D.Y) / 2, (float)Math.Ceiling((Math.Sqrt(2d) * (D.X - A.X)) / 2f) + _circlePadding);
+ }
+
+ ///
+ /// Draws the down button of the entry.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawDownButton(ICanvas canvas, RectF rectF)
+ {
+ // Adjust the rect width and height values to match the Material 3 design measurements using default width (32) and height (30) values for Android platform.
+ float defaultWidth = 32f;
+ float defaultHeight = 30f;
+
+ float widthFactor = defaultWidth / _padding;
+ float heightFactor = defaultHeight / _padding;
+
+ float widthPadding = rectF.Width / widthFactor;
+ float heightPadding = rectF.Height / heightFactor;
+
+ rectF.X = rectF.Center.X - widthPadding;
+ rectF.Y = rectF.Center.Y - (heightPadding / 2);
+ rectF.Width = (widthPadding * 2);
+ rectF.Height = heightPadding;
+
+ float x = rectF.X;
+ float y = rectF.Y;
+ float width = x + rectF.Width;
+ float height = y + rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, y);
+ path.LineTo(width, y);
+ path.LineTo(midWidth, height);
+ path.LineTo(x, y);
+ path.Close();
+ canvas.FillPath(path);
+
+ }
+
+ ///
+ /// Draws the up button of the entry.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ public void DrawUpButton(ICanvas canvas, RectF rectF) {
+ rectF.X = rectF.Center.X - _padding;
+ rectF.Y = rectF.Center.Y - (_padding / 2);
+ rectF.Width = (_padding * 2);
+ rectF.Height = _padding;
+
+ float x = rectF.X;
+ float y = rectF.Y + rectF.Height;
+ float width = x + rectF.Width;
+ float height = y - rectF.Height;
+ float midWidth = x + (rectF.Width / 2);
+
+ var path = new PathF();
+ path.MoveTo(x, y);
+ path.LineTo(width, y);
+ path.LineTo(midWidth, height);
+ path.LineTo(x, y);
+ path.Close();
+ canvas.FillPath(path);
+ }
+ }
+
+ ///
+ /// The IUpDownButtonRenderer interface defines methods for rendering various components
+ /// of a entry. Implementations of this interface are responsible for drawing
+ /// the up button, down button, clear button, and border of the entry.
+ ///
+ internal interface IUpDownButtonRenderer
+ {
+ ///
+ /// Draws the up button of the dropdown.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ void DrawUpButton(ICanvas canvas, RectF rectF);
+
+ ///
+ /// Draws the down button of the dropdown.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ void DrawDownButton(ICanvas canvas, RectF rectF);
+
+ ///
+ /// Draws the clear button.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the button's bounds.
+ void DrawClearButton(ICanvas canvas, RectF rectF);
+
+ ///
+ /// Draws the border.
+ ///
+ /// The canvas on which to draw.
+ /// The rectangle defining the border's bounds.
+ /// Indicates if the dropdown is focused.
+ /// The color of the border.
+ /// The color of the border when focused.
+ void DrawBorder(ICanvas canvas, RectF rectF, bool isFocused, Color borderColor, Color focusedBorderColor);
+ }
+
+}
diff --git a/maui/src/NumericEntry/Enum/NumberUpDownButtonAlignment.cs b/maui/src/NumericEntry/Enum/NumberUpDownButtonAlignment.cs
new file mode 100644
index 00000000..b9de0146
--- /dev/null
+++ b/maui/src/NumericEntry/Enum/NumberUpDownButtonAlignment.cs
@@ -0,0 +1,23 @@
+namespace Syncfusion.Maui.Toolkit.NumericUpDown
+{
+ ///
+ /// Used to align both icons at the left or right edge, or to align the down icon at the left and the up icon at the right.
+ ///
+ public enum UpDownButtonAlignment
+ {
+ ///
+ /// Both the Spin buttons are positioned at the Left.
+ ///
+ Left,
+
+ ///
+ /// Both the Spin buttons are positioned at the Right.
+ ///
+ Right,
+
+ ///
+ /// Spin buttons are positioned at the extreme ends.
+ ///
+ Both
+ }
+}
diff --git a/maui/src/NumericEntry/Enum/NumberUpDownPlacementMode.cs b/maui/src/NumericEntry/Enum/NumberUpDownPlacementMode.cs
new file mode 100644
index 00000000..1da07266
--- /dev/null
+++ b/maui/src/NumericEntry/Enum/NumberUpDownPlacementMode.cs
@@ -0,0 +1,21 @@
+using Syncfusion.Maui.Toolkit.NumericEntry;
+
+namespace Syncfusion.Maui.Toolkit.NumericUpDown
+{
+ ///
+ /// Defines values that specify how the spin buttons used to increment or decrement
+ /// the of a are displayed.
+ ///
+ public enum NumericUpDownPlacementMode
+ {
+ ///
+ /// The spin buttons are displayed in an expanded, horizontal orientation.
+ ///
+ Inline,
+
+ ///
+ /// The spin buttons are displayed in an expanded, vertical orientation.
+ ///
+ InlineVertical,
+ }
+}
diff --git a/maui/src/NumericEntry/Enum/PercentDisplayMode.cs b/maui/src/NumericEntry/Enum/PercentDisplayMode.cs
new file mode 100644
index 00000000..4e2345b3
--- /dev/null
+++ b/maui/src/NumericEntry/Enum/PercentDisplayMode.cs
@@ -0,0 +1,18 @@
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ /// Contains the values for the PercentDisplayMode enumeration allow to display the value with percentage computation.
+ ///
+ public enum PercentDisplayMode
+ {
+ ///
+ /// Displays the actual value with percentage symbol.
+ ///
+ Value,
+
+ ///
+ /// Displays the computed value with percentage symbol.
+ ///
+ Compute
+ }
+}
\ No newline at end of file
diff --git a/maui/src/NumericEntry/Enum/ValueChangeMode.cs b/maui/src/NumericEntry/Enum/ValueChangeMode.cs
new file mode 100644
index 00000000..ca94eb23
--- /dev/null
+++ b/maui/src/NumericEntry/Enum/ValueChangeMode.cs
@@ -0,0 +1,39 @@
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ /// Defines the behavior of value updating while typing in a text box during both the "Focus" and "Unfocus" states.
+ ///
+ public enum ValueChangeMode
+ {
+ ///
+ /// Value will be updated while the text box is in focus.
+ ///
+ OnKeyFocus,
+
+ ///
+ /// Value will be updated when the text box loses focus.
+ ///
+ OnLostFocus
+ }
+
+ ///
+ /// Enumeration representing the value states of the control.
+ ///
+ internal enum ValueStates
+ {
+ ///
+ /// The control is in its normal state.
+ ///
+ Normal,
+
+ ///
+ /// The control is at its minimum value.
+ ///
+ Minimum,
+
+ ///
+ /// The control is at its maximum value.
+ ///
+ Maximum
+ }
+}
diff --git a/maui/src/NumericEntry/Event/ValueChangedEventArgs.cs b/maui/src/NumericEntry/Event/ValueChangedEventArgs.cs
new file mode 100644
index 00000000..29233330
--- /dev/null
+++ b/maui/src/NumericEntry/Event/ValueChangedEventArgs.cs
@@ -0,0 +1,30 @@
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ /// Provides event data for the event.
+ ///
+ public class NumericEntryValueChangedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Contains the new value.
+ /// Contains the old value.
+ internal NumericEntryValueChangedEventArgs(double? newValue, double? oldValue)
+ {
+ NewValue = newValue;
+ OldValue = oldValue;
+ }
+
+ ///
+ /// Gets the new to be set for a .
+ ///
+ public double? NewValue { get; }
+
+ ///
+ /// Gets the old being replaced in .
+ ///
+ public double? OldValue { get; }
+ }
+
+}
diff --git a/maui/src/NumericEntry/Resources/SfNumericEntry.en-US.resx b/maui/src/NumericEntry/Resources/SfNumericEntry.en-US.resx
new file mode 100644
index 00000000..d25e8412
--- /dev/null
+++ b/maui/src/NumericEntry/Resources/SfNumericEntry.en-US.resx
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ return
+
+
+ go
+
+
+ done
+
+
+ send
+
+
+ search
+
+
+ next
+
+
\ No newline at end of file
diff --git a/maui/src/NumericEntry/Resources/SfNumericEntryResources.cs b/maui/src/NumericEntry/Resources/SfNumericEntryResources.cs
new file mode 100644
index 00000000..c67d6cc5
--- /dev/null
+++ b/maui/src/NumericEntry/Resources/SfNumericEntryResources.cs
@@ -0,0 +1,45 @@
+using System.Globalization;
+using Syncfusion.Maui.Toolkit.Localization;
+
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ ///
+ ///
+ public class SfNumericEntryResources : LocalizationResourceAccessor
+ {
+ ///
+ /// Gets the culture info.
+ ///
+ /// The culture.
+ internal static CultureInfo CultureInfo
+ {
+ get
+ {
+ return CultureInfo.CurrentUICulture;
+ }
+ }
+
+ ///
+ /// Gets the localized string.
+ ///
+ /// Text type.
+ /// The string.
+ internal static string GetLocalizedString(string text)
+ {
+ string? value = string.Empty;
+ if (ResourceManager != null)
+ {
+ Culture = CultureInfo;
+ value = GetString(text);
+ }
+
+ if (string.IsNullOrEmpty(value))
+ {
+ value = text;
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
new file mode 100644
index 00000000..134efc8b
--- /dev/null
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -0,0 +1,3169 @@
+using System.Globalization;
+using System.Text.RegularExpressions;
+using Syncfusion.Maui.Toolkit.EffectsView;
+using Syncfusion.Maui.Toolkit.EntryRenderer;
+using Syncfusion.Maui.Toolkit.EntryView;
+using Syncfusion.Maui.Toolkit.TextInputLayout;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+using Path = Microsoft.Maui.Controls.Shapes.Path;
+using PointerEventArgs = Syncfusion.Maui.Toolkit.Internals.PointerEventArgs;
+#if WINDOWS
+using Microsoft.UI.Xaml.Controls;
+using Windows.Globalization.NumberFormatting;
+using Windows.System;
+using System.Collections.Generic;
+#elif ANDROID
+using Android.Text;
+#elif MACCATALYST || IOS
+using UIKit;
+using Foundation;
+using Microsoft.Maui.ApplicationModel;
+#if IOS
+using CoreGraphics;
+#endif
+#endif
+
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ public partial class SfNumericEntry
+ {
+ #region Private Methods
+
+ ///
+ /// Validate the parent is a TextInputLayout or not.
+ ///
+ void ValidateParentIsTextInputLayout()
+ {
+ for (Element? parentElement = Parent; parentElement != null; parentElement = parentElement.Parent)
+ {
+ if (parentElement is SfTextInputLayout inputLayout && inputLayout.Content == this)
+ {
+ IsTextInputLayout = true;
+ _textInputLayout = inputLayout;
+ UpdateTextInputLayoutUI();
+ MinimumHeightRequest = 0;
+ BackgroundColor = Colors.Transparent;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Updates the current touch point's position based on the provided touch event data.
+ /// Adjusts the touch point for RTL (right-to-left) layouts if necessary.
+ ///
+ /// The touch point on the screen.
+ void UpdateTouchPoint(Point touch)
+ {
+#if WINDOWS
+ _touchPoint = IsRTL() ? new Point(Width - touch.X, touch.Y) : touch;
+#else
+ _touchPoint = touch;
+#endif
+ }
+
+ ///
+ /// Handles the logic when a touch press is detected. This includes handling
+ /// button presses and starting a long-press timer if applicable.
+ ///
+ /// The event arguments containing details about the touch press interaction.
+ void HandlePressed(PointerEventArgs e)
+ {
+#if WINDOWS
+ // Check if the text box is focused on Windows before doing anything.
+ if (_textBox != null && _textBox.IsFocused)
+ {
+ _isFocus = true;
+ }
+#else
+ // Set focus for other platforms when a touch press occurs.
+ Focus();
+#endif
+ }
+
+ ///
+ /// Handles the logic when a touch release or cancellation is detected, stopping
+ /// the long-press timer and triggering release-related actions.
+ ///
+ /// The event arguments detailing the touch release or cancel interaction.
+ void HandleReleasedOrCancelled(PointerEventArgs e)
+ {
+ if (e.Action == PointerActions.Released)
+ {
+ if (ClearButtonClicked(_touchPoint))
+ {
+ OnClearButtonTouchUp(e);
+ }
+ ClearHighlightIfNecessary();
+ }
+ }
+
+ static Brush GetDefaultStroke()
+ {
+#if ANDROID
+ return new SolidColorBrush(Color.FromRgba(100, 100, 100, 255));
+#elif WINDOWS
+ return new SolidColorBrush(Color.FromRgba(141, 141, 141, 255));
+#else
+ return new SolidColorBrush(Colors.LightGray);
+#endif
+ }
+
+ ///
+ /// Initializes platform-specific settings and resources.
+ ///
+ void SetRendererBasedOnPlatform()
+ {
+#if ANDROID
+ _entryRenderer = new MaterialSfEntryRenderer() { _clearButtonPadding = 12 };
+#elif WINDOWS
+ _entryRenderer = new FluentSfEntryRenderer();
+#else
+ _entryRenderer = new CupertinoSfEntryRenderer() { _borderStrokeSize = 6 };
+#endif
+ }
+
+ ///
+ /// Configures the positioning and size of the clear button within the given bounding rectangle.
+ ///
+ /// The bounding rectangle that defines the available space for positioning the clear button.
+ void ConfigureClearButton(RectF bounds)
+ {
+ // Exit method early if the clear button is not visible to avoid unnecessary calculations.
+ if (!_isClearButtonVisible)
+ {
+ return;
+ }
+
+ // Determine if the layout direction is RTL (right-to-left).
+ bool isRTL = IsRTL();
+
+ // Set the X position based on RTL status; place on left for RTL, right otherwise.
+ _clearButtonRectF.X = isRTL ? 0 : (bounds.Width - ButtonSize);
+
+ // Set other dimensions and properties of the clear button.
+ _clearButtonRectF.Y = bounds.Center.Y - (ButtonSize / 2);
+ _clearButtonRectF.Width = ButtonSize;
+ _clearButtonRectF.Height = ButtonSize;
+
+ // Adjusts button space to the size of the button.
+ _buttonSpace = ButtonSize;
+
+ // Clear existing semantics nodes to prepare for the new configuration.
+ _numericEntrySemanticsNodes.Clear();
+
+ // Invalidate semantics to ensure the changes are reflected.
+ InvalidateSemantics();
+ }
+
+ ///
+ /// Sets the margin for the text box within the numeric entry control based on its layout requirements.
+ ///
+ void SetTextBoxMargin()
+ {
+ if (_textBox != null)
+ {
+ _textBox.ButtonSize = ButtonSize + 4;
+ int defaultMargin = IsTextInputLayout ? 0 : 10;
+ double rightMargin = _buttonSpace;
+
+ #if ANDROID || IOS || MACCATALYST
+ _textBox.Margin = new Thickness(defaultMargin, 0, rightMargin, 0);
+ #else
+ _textBox.Margin = new Thickness(0, 0, rightMargin, 0);
+ #endif
+ }
+ }
+
+ ///
+ /// Clears the collection of effect bounds, typically used to reset or remove visual effects on the control.
+ ///
+ void ClearEffectsBoundsCollection()
+ {
+ if (_effectsRenderer is null)
+ {
+ return;
+ }
+ _effectsRenderer.RippleBoundsCollection.Clear();
+ _effectsRenderer.HighlightBoundsCollection.Clear();
+ }
+
+ ///
+ /// Draws the path for the clear button using the specified canvas and path.
+ ///
+ /// The canvas on which to draw the clear button path.
+ /// The path that defines the shape and outline of the clear button.
+ void DrawClearButtonPath(ICanvas canvas, Path clearButtonPath)
+ {
+ PathF path = clearButtonPath.GetPath();
+ canvas.SaveState();
+ canvas.FillColor = clearButtonPath.Fill is SolidColorBrush solidColorBrushFill ? solidColorBrushFill.Color : Colors.Transparent;
+ canvas.StrokeColor = clearButtonPath.Stroke is SolidColorBrush solidColorBrushStroke ? solidColorBrushStroke.Color : ClearIconStrokeColor;
+ canvas.ClipRectangle(UpdatePathRect());
+ canvas.Translate(_clearButtonRectF.Center.X - path.Bounds.Center.X, _clearButtonRectF.Center.Y - path.Bounds.Center.Y);
+ canvas.FillPath(path, WindingMode.EvenOdd);
+ canvas.DrawPath(path);
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Updates and returns the bounding rectangle used for positioning drawable elements.
+ ///
+ /// A rectangle that defines the area for drawing paths or other elements within the control.
+ RectF UpdatePathRect()
+ {
+ RectF clipSize = new()
+ {
+ Width = _clearButtonRectF.Width / 2,
+ Height = _clearButtonRectF.Height / 2
+ };
+ clipSize.X = _clearButtonRectF.Center.X - clipSize.Width / 2;
+ clipSize.Y = _clearButtonRectF.Center.Y - clipSize.Height / 2;
+
+ return clipSize;
+ }
+
+ ///
+ /// Initializes the settings of the given canvas, preparing it for drawing operations.
+ ///
+ /// The canvas to be initialized for rendering content.
+ void InitializeCanvas(ICanvas canvas)
+ {
+ canvas.Alpha = 1f;
+ canvas.FillColor = Colors.Black;
+ canvas.StrokeColor = Colors.Black;
+ }
+
+ ///
+ /// Draws the clear button on the specified canvas.
+ ///
+ /// The canvas on which the clear button is drawn.
+ void DrawClearButton(ICanvas canvas)
+ {
+ if (!_isClearButtonVisible)
+ {
+ return;
+ }
+
+ if (ClearButtonPath is not null)
+ {
+ DrawClearButtonPath(canvas, ClearButtonPath);
+ }
+ else
+ {
+ canvas.StrokeColor = ClearButtonColor;
+ _entryRenderer?.DrawClearButton(canvas, _clearButtonRectF);
+ }
+ }
+
+ ///
+ /// Draws a border around the control using the specified canvas and rectangle.
+ ///
+ /// The canvas on which the border is drawn.
+ /// The rectangle defining the boundary area for the border.
+ void DrawBorder(ICanvas canvas, Rect dirtyRect)
+ {
+ if (_textBox is null || _textBox.FocusedStroke is null || (Stroke is not SolidColorBrush stroke) || !ShowBorder)
+ {
+ return;
+ }
+
+ Color strokeColor = Colors.Equals(stroke.Color, (GetDefaultStroke() as SolidColorBrush)?.Color)
+ ? _textBox.FocusedStroke
+ : stroke.Color;
+
+ _entryRenderer?.DrawBorder(canvas, dirtyRect, _textBox.IsFocused, stroke.Color, strokeColor);
+ }
+
+ ///
+ /// Draws a effects.
+ ///
+ /// The canvas on which the effects is drawn.
+ void DrawEffects(ICanvas canvas)
+ {
+ if (_effectsRenderer is null)
+ {
+ return;
+ }
+
+ _effectsRenderer.ControlWidth = Width;
+ _effectsRenderer.IsRTL = IsRTL();
+ _effectsRenderer.DrawEffects(canvas);
+ }
+
+ ///
+ /// Updates the semantic sizes for the clear, down, and up buttons
+ /// based on their current rectangle dimensions.
+ ///
+ void UpdateSemanticsSizes()
+ {
+ _clearButtonSemanticsSize = new Size(_clearButtonRectF.Width, _clearButtonRectF.Height);
+ }
+
+ ///
+ /// Checks whether the current semantic data is still valid and does not need rebuilding.
+ ///
+ /// True if the semantic data is current, otherwise false.
+ bool SemanticsDataIsCurrent()
+ {
+ return _numericEntrySemanticsNodes.Count != 0 &&
+ _clearButtonSemanticsSize == new Size(_clearButtonRectF.Width, _clearButtonRectF.Height);
+ }
+
+ ///
+ /// Updates the properties of the entry view.
+ ///
+ void UpdateEntryProperties()
+ {
+ if (_textBox != null)
+ {
+ _textBox.FontAttributes = FontAttributes;
+ _textBox.FontFamily = FontFamily;
+ _textBox.FontAutoScalingEnabled = FontAutoScalingEnabled;
+ _textBox.FontSize = FontSize;
+ _textBox.TextColor = TextColor;
+ _textBox.HorizontalTextAlignment = HorizontalTextAlignment;
+ _textBox.VerticalTextAlignment = VerticalTextAlignment;
+#if ANDROID
+ _textBox.HeightRequest = HeightRequest;
+#endif
+ }
+ }
+
+ ///
+ /// Sets the data binding for the and .
+ ///
+ void SetBinding()
+ {
+ if (_textBox != null)
+ {
+ _textBox.BindingContext = this;
+ _textBox.SetBinding(SfEntryView.IsEnabledProperty, "IsEnabled");
+ _textBox.SetBinding(SfEntryView.IsVisibleProperty, "IsVisible");
+ _textBox.SetBinding(SfEntryView.CursorPositionProperty, "CursorPosition", BindingMode.TwoWay);
+ _textBox.SetBinding(SfEntryView.SelectionLengthProperty, "SelectionLength", BindingMode.TwoWay);
+ }
+ }
+
+ ///
+ /// Initializes the elements needed for the control's GUI and behavior.
+ ///
+ void InitializeElements()
+ {
+ _effectsRenderer ??= new EffectsRenderer(this) { RippleAnimationDuration = 100};
+
+ UpdateEntryProperties();
+ UpdateClearButtonVisibility();
+ SetRendererBasedOnPlatform();
+ GetMinimumSize();
+ HookEvents();
+ }
+
+#if WINDOWS
+
+ ///
+ /// Helps to get the core window based on the configuration.
+ ///
+ /// The virtual key.
+ /// The CoreVirtualKeyStates.
+ static Windows.UI.Core.CoreVirtualKeyStates GetVirtualKeyStates(VirtualKey virtualKey)
+ {
+ return Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(virtualKey);
+ }
+
+ ///
+ /// Handles the event when a pointer enters the control's area.
+ ///
+ /// The source of the event.
+ /// A PointerRoutedEventArgs that contains the event data.
+ void OnPointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
+ {
+ if (_effectsRenderer != null && _effectsRenderer.HighlightBounds.Width > 0 && _effectsRenderer.HighlightBounds.Height > 0)
+ {
+ _effectsRenderer.RemoveHighlight();
+ }
+ if (_textBox != null && !_textBox.IsFocused)
+ {
+ VisualStateManager.GoToState(this, "PointerOver");
+ }
+ }
+
+ ///
+ /// Handles the event when a pointer exits the control's area.
+ ///
+ /// The source of the event.
+ /// A PointerRoutedEventArgs that contains the event data.
+ void OnPointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
+ {
+ if (_textBox != null && !_textBox.IsFocused)
+ {
+ VisualStateManager.GoToState(this, "Normal");
+ }
+ }
+
+ ///
+ /// Sets focus to the input view.
+ ///
+ void SetInputViewFocus()
+ {
+ _textBox?.Focus();
+ _isFocus = false;
+ }
+#endif
+
+#if ANDROID
+
+ ///
+ /// Handles the BeforeTextChanged event for the Android entry control.
+ ///
+ /// The source of the event.
+ /// A TextChangedEventArgs that contains the event data.
+ void AndroidEntry_BeforeTextChanged(object? sender, Android.Text.TextChangedEventArgs e)
+ {
+ var entry = sender as AndroidX.AppCompat.Widget.AppCompatEditText;
+ if (entry != null && e.Text != null)
+ {
+ _previousText = e.Text.ToString() ?? (AllowNull ? "" : "0");
+ }
+ _shiftCursor = false;
+ if (entry != null && entry.Text != null && entry.Text.Contains('-', StringComparison.Ordinal) && _textBox != null && _textBox.CursorPosition == 0)
+ {
+ _shiftCursor = true;
+ }
+ }
+
+ ///
+ /// Determines whether the current text change is valid.
+ ///
+ /// True if the change is valid; otherwise, false.
+ bool IsValidChange()
+ {
+ return string.IsNullOrEmpty(_previousText) ||
+ double.TryParse(_previousText.Replace(GetNumberDecimalSeparator(GetNumberFormat()),
+ GetNumberDecimalSeparator(CultureInfo.CurrentUICulture.NumberFormat), StringComparison.Ordinal), out _);
+ }
+
+ ///
+ /// Determines if a new character has been typed.
+ ///
+ /// Length of the text before the change.
+ /// Length of the text after the change.
+ /// The starting position of the selection.
+ /// True if a new character has been typed, false otherwise.
+ bool IsNewCharacterTyped(int lengthBefore, int lengthAfter, int selectionStart)
+ {
+ return ((lengthAfter == 1 && lengthBefore == 0) || ((lengthAfter - lengthBefore) == 1) || (lengthBefore > 0 && lengthAfter == 1)) && selectionStart > 0;
+ }
+
+ ///
+ /// Retrieves the character that was typed.
+ ///
+ /// The current text in the input field.
+ /// The starting position of the change.
+ /// Length of the text before the change.
+ /// Length of the text after the change.
+ /// The EditText control.
+ /// The character that was typed.
+ char GetTypedChar(string text, int start, int lengthBefore, int lengthAfter, AndroidX.AppCompat.Widget.AppCompatEditText entry)
+ {
+ if (start > 0)
+ {
+ int charIndex = (lengthAfter > 1) ? lengthBefore : 0;
+ if (text.Length > start)
+ {
+ return text.Remove(0, start)[charIndex];
+ }
+ }
+ else if (start == 0)
+ {
+ return text.Substring(entry.SelectionStart - 1, 1)[0];
+ }
+ return '\0';
+ }
+
+ ///
+ /// Handles the input of a new character.
+ ///
+ /// The current text in the input field.
+ /// The starting position of the change.
+ /// Length of the text before the change.
+ /// Length of the text after the change.
+ /// The EditText control.
+ /// The current position of the caret.
+ /// Indicates if the number is negative.
+ /// The decimal separator for the current culture.
+ void HandleNewCharacter(string text, int start, int lengthBefore, int lengthAfter, AndroidX.AppCompat.Widget.AppCompatEditText entry, int caretPosition, bool isNegative, string decimalSeparator)
+ {
+ ////If we typed new character below condition become true
+ char typedChar = GetTypedChar(text, start, lengthBefore, lengthAfter, entry);
+
+ if (typedChar.Equals(decimalSeparator[0]))
+ {
+ HandleDecimalSeparatorInput(caretPosition, decimalSeparator);
+ }
+ else if (typedChar.Equals('-'))
+ {
+ HandleNegativeInput(isNegative, caretPosition);
+ }
+ else if (char.IsNumber(typedChar))
+ {
+ HandleNumbers(typedChar.ToString(), caretPosition, decimalSeparator);
+ }
+ else
+ {
+ RevertToPreviousText(caretPosition);
+ }
+ }
+
+ ///
+ /// Handles the input of a decimal separator.
+ ///
+ /// The current position of the caret.
+ /// The decimal separator for the current culture.
+ void HandleDecimalSeparatorInput(int caretPosition, string decimalSeparator)
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+
+ if ((_maximumPositiveFractionDigit != 0 && !_textBox.Text.Contains('-', StringComparison.Ordinal)) ||
+ (_maximumNegativeFractionDigit != 0 && _textBox.Text.Contains('-', StringComparison.Ordinal)))
+ {
+ HandleDecimalSeparator(decimalSeparator, caretPosition);
+ }
+ else
+ {
+ RevertToPreviousText(caretPosition);
+ }
+ }
+
+ ///
+ /// Reverts the text to its previous state and updates the cursor position.
+ ///
+ /// The current position of the caret.
+ void RevertToPreviousText(int caretPosition)
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+ _textBox.Text = _previousText;
+ _cursorPosition = caretPosition;
+ }
+
+ ///
+ /// Handles the input of a negative sign.
+ ///
+ /// Indicates if the number is already negative.
+ /// The current position of the caret.
+ void HandleNegativeInput(bool isNegative, int caretPosition)
+ {
+ //HandleNegativeKey(isNegative, caretPosition);
+ //// Unable to achieve -0, app breaks without any exception.
+ if (_textBox is not null && string.IsNullOrEmpty(_previousText))
+ {
+ _textBox.Text = "-0";
+ _cursorPosition = 2;
+ }
+ else
+ {
+ HandleNegativeKey(isNegative, caretPosition);
+ }
+ }
+
+ ///
+ /// Handles the deletion of a character.
+ ///
+ /// The current position of the caret.
+ /// The decimal separator for the current culture.
+ void HandleDeletion(int caretPosition, string decimalSeparator)
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+
+ if (double.TryParse(_textBox.Text, out double result) && Maximum != result)
+ {
+ _cursorPosition = caretPosition;
+ }
+ }
+
+ ///
+ /// Validates text input to ensure it conforms with desired number formatting.
+ ///
+ void ValidateTextChanged(object? sender, Android.Text.TextChangedEventArgs e)
+ {
+ if (e is not null && sender is AndroidX.AppCompat.Widget.AppCompatEditText entry &&
+ !string.IsNullOrEmpty(entry.Text) && entry.IsFocused && _textBox != null && IsValidChange())
+ {
+ string? text = e.Text?.ToString();
+ int lengthBefore = e.BeforeCount;
+ int lengthAfter = e.AfterCount;
+ int start = e.Start;
+ int caretPosition = _shiftCursor ? e.Start + 1 : e.Start;
+ bool isNegative = IsNegative(_previousText, GetNumberFormat());
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ _selectionLength = lengthBefore - lengthAfter + 1;
+ _selectionLength = _selectionLength < 0 ? 0 : _selectionLength;
+ if (text == null)
+ {
+ return;
+ }
+
+ if (IsNewCharacterTyped(lengthBefore, lengthAfter, entry.SelectionStart))
+ {
+ HandleNewCharacter(text, start, lengthBefore, lengthAfter, entry, caretPosition, isNegative, decimalSeparator);
+ }
+ else if (lengthBefore - lengthAfter == 1)
+ {
+ HandleDeletion(caretPosition, decimalSeparator);
+ }
+ else if (lengthBefore > lengthAfter)
+ {
+ HandleMultipleCharactersDeletion(text, caretPosition, lengthBefore);
+ }
+ else if (IsMultipleCharactersInsertion(lengthBefore, lengthAfter))
+ {
+ HandleMultipleCharactersInsertion(text, caretPosition, lengthAfter);
+ }
+ else
+ {
+ _cursorPosition = caretPosition + lengthBefore;
+ }
+
+ _previousText = _textBox.Text;
+
+ }
+ }
+
+ ///
+ /// Determines if multiple characters are being inserted.
+ ///
+ /// Length of the text before the change.
+ /// Length of the text after the change.
+ /// True if multiple characters are being inserted, false otherwise.
+ bool IsMultipleCharactersInsertion(int lengthBefore, int lengthAfter)
+ {
+ return lengthBefore < lengthAfter && lengthAfter - lengthBefore > 1;
+ }
+
+ ///
+ /// Handles the insertion of multiple characters.
+ ///
+ /// The current text in the input field.
+ /// The current position of the caret.
+ /// Length of the text after the change.
+ void HandleMultipleCharactersInsertion(string text, int caretPosition, int lengthAfter)
+ {
+ if (double.TryParse(text, out _))
+ {
+ _cursorPosition = caretPosition + lengthAfter;
+ }
+ else
+ {
+ if (_textBox is not null)
+ {
+ _textBox.Text = _previousText;
+ }
+ }
+ }
+
+ ///
+ /// Handles the deletion of multiple characters.
+ ///
+ /// The current text in the input field.
+ /// The current position of the caret.
+ void HandleMultipleCharactersDeletion(string text, int caretPosition, int lengthBefore)
+ {
+ if (!double.TryParse(text, out _))
+ {
+ RevertToPreviousText(caretPosition);
+ }
+ else
+ {
+ _cursorPosition = caretPosition + lengthBefore;
+ }
+ }
+
+ ///
+ /// Handles the AfterTextChanged event for the Android entry control.
+ ///
+ /// The source of the event.
+ /// An AfterTextChangedEventArgs that contains the event data.
+ void AndroidEntry_AfterTextChanged(object? sender, Android.Text.AfterTextChangedEventArgs e)
+ {
+ var entry = sender as AndroidX.AppCompat.Widget.AppCompatEditText;
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ if (entry != null && (entry.Text == "-" || entry.Text == decimalSeparator))
+ {
+ if (!IsSamsungDevice() && _textBox != null)
+ {
+ _textBox.Text = AllowNull ? string.Empty : "0";
+ }
+ }
+ if (IsSamsungDevice() && _textBox != null && _textBox.Text.Length > 1)
+ {
+ if (_textBox.Text[0] == decimalSeparator[0] && _textBox.Text.LastIndexOf(decimalSeparator) == 0)
+ {
+ _textBox.Text = "0" + _textBox.Text;
+ }
+ }
+ if (!AllowNull && entry != null && entry.Text == string.Empty && _textBox != null)
+ {
+ _textBox.Text = "0";
+ _cursorPosition = 1;
+ }
+ if (ValueChangeMode == ValueChangeMode.OnLostFocus && _textBox != null)
+ {
+ _textBox.CursorPosition = _cursorPosition;
+ _cursorPosition = 0;
+ }
+ }
+
+ ///
+ /// Validate the device is Samsung.
+ ///
+ /// It returns True if the device is Samsung
+ bool IsSamsungDevice()
+ {
+ return string.Equals(global::Android.OS.Build.Manufacturer, "samsung", StringComparison.OrdinalIgnoreCase);
+ }
+
+#endif
+
+#if MACCATALYST
+ ///
+ /// Occurs when key is pressed.
+ ///
+ /// The KeyEventArgs.
+ void OnTextBoxPreviewKeyDown(KeyEventArgs e)
+ {
+ if (_textBox == null || _textBox.IsEnabled)
+ {
+ return;
+ }
+
+ if (_textBox.IsReadOnly && e.Key != KeyboardKey.Up && e.Key != KeyboardKey.Down
+ && e.Key != KeyboardKey.PageUp && e.Key != KeyboardKey.PageDown)
+ {
+ return;
+ }
+
+ //Reset SelectionStart value when it increased to higher than text's length.
+ if (_textBox.Text.Length < _textBox.CursorPosition)
+ {
+ _textBox.CursorPosition = _textBox.Text.Length;
+ }
+
+ int caretPosition = _textBox.CursorPosition;
+ bool isNegative = IsNegative(_textBox.Text, GetNumberFormat());
+
+ if (e.Key == KeyboardKey.Left || e.Key == KeyboardKey.Right || e.Key == KeyboardKey.Home ||
+ e.Key == KeyboardKey.End || e.Key == KeyboardKey.Up || e.Key == KeyboardKey.Down ||
+ e.Key == KeyboardKey.PageUp || e.Key == KeyboardKey.PageDown)
+ {
+ // Handles when the arrow key or page up/down key is pressed.
+ e.Handled = HandleNavigation(e.Key, isNegative, caretPosition);
+ }
+ else
+ {
+ e.Handled = false;
+ }
+
+
+ }
+#endif
+
+#if MACCATALYST || IOS
+
+ ///
+ /// Validates and handles text changes in the UITextField.
+ ///
+ /// The UITextField being modified.
+ /// The range of text being replaced.
+ /// The string to replace the text in the specified range.
+ /// Always returns false to indicate that the text field should not handle the text change itself.
+ bool ValidateTextChanged(UITextField textField, NSRange range, string replacementString)
+ {
+ if(textField.Text==null)
+ {
+ return false;
+ }
+ _previousText = textField.Text;
+ char typedChar;
+ int caretPosition = (int)range.Location;
+ bool isNegative = IsNegative(_previousText, GetNumberFormat());
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+
+ if (string.IsNullOrEmpty(replacementString) && range.Length > 1)
+ {
+ PerformCut(caretPosition);
+ }
+ else if (string.IsNullOrEmpty(replacementString))
+ {
+ HandleBackspace(caretPosition + 1, decimalSeparator);
+ }
+ //If we typed new character below condition become true
+ else if (replacementString.Length == 1)
+ {
+ typedChar = replacementString[0];
+
+ if (typedChar.Equals(decimalSeparator[0]))
+ {
+ if ((_maximumPositiveFractionDigit != 0 && _textBox != null && !_textBox.Text.Contains('-', StringComparison.Ordinal)) || (_maximumNegativeFractionDigit != 0 && _textBox != null && _textBox.Text.Contains('-', StringComparison.Ordinal)))
+ {
+ HandleDecimalSeparator(decimalSeparator, caretPosition);
+ }
+ }
+ else if (typedChar.Equals('-'))
+ {
+ if (string.IsNullOrEmpty(_previousText) && _textBox != null)
+ {
+ _textBox.Text = "-0";
+ _textBox.CursorPosition = 2;
+ }
+ else
+ {
+ HandleNegativeKey(isNegative, caretPosition);
+ }
+ }
+ else if (char.IsNumber(typedChar))
+ {
+ HandleNumbers(replacementString, caretPosition, decimalSeparator);
+ }
+ else
+ {
+ textField.Text = _previousText;
+ }
+
+ }
+ else if (replacementString.Length > 1)
+ {
+ OnTextBoxPaste(textField, caretPosition);
+ }
+ return false;
+ }
+
+ ///
+ /// Performs a cut operation on the text in the associated text box.
+ ///
+ /// The current caret position in the text box.
+ void PerformCut(int caretPosition)
+ {
+ if (_textBox != null)
+ {
+ string selectedText = _textBox.Text.Substring(caretPosition, _textBox.SelectionLength);
+ Microsoft.Maui.ApplicationModel.DataTransfer.Clipboard.SetTextAsync(selectedText);
+ _textBox.Text = RemoveSelectedText(_previousText, selectedText, caretPosition, GetNumberFormat());
+ _textBox.CursorPosition = caretPosition;
+ }
+ }
+
+ ///
+ /// Occurs when text is pasted in Entry.
+ ///
+ /// NumberBox control.
+ /// character's cursor position
+ async void OnTextBoxPaste(object? sender, int caretPosition)
+ {
+ if (_textBox != null)
+ {
+ if (Microsoft.Maui.ApplicationModel.DataTransfer.Clipboard.HasText)
+ {
+ try
+ {
+ // Validate the input and insert the copied text in cursor position.
+ var copiedText = await Microsoft.Maui.ApplicationModel.DataTransfer.Clipboard.GetTextAsync();
+ if (!string.IsNullOrEmpty(copiedText))
+ {
+ copiedText = ValidatePastedText(copiedText, GetNumberFormat());
+ string displayText = _previousText;
+#if ANDROID
+ string selectedText = _previousText.Substring(caretPosition, _selectionLength);
+ _cursorPosition = caretPosition;
+#else
+ string selectedText = _previousText.Substring(_textBox.CursorPosition, _textBox.SelectionLength);
+ caretPosition = _textBox.CursorPosition;
+#endif
+ if (!string.IsNullOrEmpty(copiedText) && CanPaste(copiedText))
+ {
+ InsertNumbers(displayText, selectedText, copiedText, caretPosition);
+ }
+ }
+ }
+ catch (System.Exception)
+ {
+ throw;
+ }
+ }
+ }
+ }
+#endif
+
+ ///
+ /// Validates and updates the current text input by resetting the value when it goes beyond allowable limits.
+ ///
+ static double? ValidateAndUpdateValue(SfNumericEntry numberBox, double? newValue)
+ {
+ bool isFocused = numberBox.IsFocused;
+ bool onKeyFocusMode = numberBox.ValueChangeMode == ValueChangeMode.OnKeyFocus;
+ if(!isFocused && newValue != null && !double.IsNaN((double)newValue))
+ {
+ newValue = numberBox.ValidateMinMax(newValue);
+ }
+
+ if (isFocused && onKeyFocusMode && newValue != null && !double.IsNaN((double)newValue))
+ {
+ newValue = Math.Clamp((double)newValue, double.MinValue, numberBox.Maximum);
+ }
+
+ numberBox.SetValue(ValueProperty, newValue);
+ return newValue;
+ }
+
+ ///
+ /// Raises the ValueChanged event for the SfNumericEntry control.
+ ///
+ /// The SfNumericEntry control.
+ /// The previous value.
+ /// The new value.
+ static void RaiseValueChangedEvent(SfNumericEntry numberBox, double? oldValue, double? newValue)
+ {
+ var valueChangedEventArgs = new NumericEntryValueChangedEventArgs(newValue, oldValue);
+ numberBox.ValueChanged?.Invoke(numberBox, valueChangedEventArgs);
+ }
+
+ ///
+ /// Converts various numeric types and strings to a nullable double.
+ ///
+ /// The value to be converted.
+ /// The previous value, used as fallback for string conversion.
+ /// A nullable double representation of the input value.
+ static double? ConvertToDouble(object newValue, object oldValue)
+ {
+ if (newValue is int or byte or short or ushort or uint or double or long or ulong or float)
+ {
+ return Convert.ToDouble(newValue);
+ }
+
+ if (newValue is string stringValue)
+ {
+ return double.TryParse(stringValue, out double result) ? result : Convert.ToDouble((double?)oldValue);
+ }
+
+ if (!(newValue is not char))
+ {
+ return Convert.ToDouble((char)newValue);
+ }
+
+ return newValue as double?;
+ }
+
+ ///
+ /// Updates the text box display and button colors of the SfNumericEntry control.
+ ///
+ /// The SfNumericEntry control to update.
+ /// The new value to be displayed.
+ static void UpdateTextBoxAndButtonColors(SfNumericEntry numberBox, double? newValue)
+ {
+#if WINDOWS
+ numberBox.UpdateDisplayText(newValue,resetCursor: false);
+#else
+ numberBox.UpdateDisplayText(newValue);
+#endif
+
+ if ((newValue == null || double.IsNaN(newValue.Value)) && numberBox._textBox != null)
+ {
+ double? defaultValue = numberBox.ValidateMinMax(0.0);
+ numberBox._textBox.Text = numberBox.AllowNull ? string.Empty :
+ defaultValue?.ToString(numberBox.GetNumberFormat());
+ }
+
+ numberBox.UpdateButtonColor(newValue);
+
+ if (numberBox._textBox != null && !numberBox.IsFocused && !numberBox._textBox.IsFocused)
+ {
+ var isValueChange = newValue != numberBox.Value;
+ numberBox.FormatValue(isValueChange);
+ }
+ numberBox._valueUpdating = false;
+ }
+
+#if WINDOWS
+ ///
+ /// Retrieves the grandparent element of the _textBox control.
+ ///
+ void GetParentElement()
+ {
+ if (_textBox is not null && _textBox.Handler is Microsoft.Maui.Handlers.EntryHandler handler && handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox nativeTextBox)
+ {
+ _grandParentElement = (nativeTextBox.Parent as Microsoft.UI.Xaml.FrameworkElement)?.Parent as Microsoft.Maui.Platform.WrapperView;
+ }
+ }
+
+ ///
+ /// Sets the flow direction of the _grandParentElement based on the text direction (RTL or LTR).
+ ///
+ void SetFlowDirection()
+ {
+ if (_grandParentElement is not null)
+ {
+ // Set the flow direction based on RTL or LTR
+ _grandParentElement.FlowDirection = IsRTL() ? Microsoft.UI.Xaml.FlowDirection.RightToLeft
+ : Microsoft.UI.Xaml.FlowDirection.LeftToRight;
+ }
+ }
+#endif
+
+#if MACCATALYST || IOS
+ ///
+ /// Returns the valid entered text to the Value of NumericEntry.
+ ///
+ void GetValidText(string accentKeys)
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+ for (int i = 0; i < accentKeys.Length; i++)
+ {
+ if (_textBox.Text.Contains(accentKeys[i], StringComparison.Ordinal))
+ {
+ _isNotValidText = true;
+ return;
+ }
+ }
+ _isNotValidText = false;
+ }
+#endif
+
+ ///
+ /// Handles the focus event on the text box.
+ ///
+ /// The source of the event.
+ /// Focus event arguments.
+ void TextBoxOnGotFocus(object? sender, FocusEventArgs e)
+ {
+#if ANDROID
+ if (_isFirstFocus && (sender is SfEntryView entry) && entry != null && entry.Handler != null && entry.Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry)
+ {
+ androidEntry.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagSigned | InputTypes.NumberFlagDecimal;
+ androidEntry.KeyListener = global::Android.Text.Method.DigitsKeyListener.GetInstance(KEYS);
+ _isFirstFocus = false;
+ }
+#endif
+ SetValue(VisualElement.IsFocusedPropertyKey, e.IsFocused);
+ RaiseFocusedEvent(e);
+ OnGotFocus();
+
+ UpdateClearButtonVisibility();
+ }
+
+ ///
+ /// Updates the color of the button based on the provided value.
+ ///
+ /// A nullable double that determines the color change.
+ /// If null, the button may revert to a default color.
+ internal virtual void UpdateButtonColor(double? value)
+ {
+
+ }
+
+ ///
+ /// Handles a key press event.
+ ///
+ /// The keyboard key that was pressed.
+ /// True if the key press was handled; otherwise, false.
+ internal virtual bool HandleKeyPressed(KeyboardKey key)
+ {
+ return false;
+ }
+
+ ///
+ /// Handles a key press event for paging operations.
+ ///
+ /// The keyboard key that was pressed.
+ /// True if the key press was handled; otherwise, false.
+ internal virtual bool HandleKeyPagePressed(KeyboardKey key)
+ {
+ return false;
+ }
+
+ ///
+ /// Handles the lost focus event on the text box.
+ ///
+ /// The source of the event.
+ /// Focus event arguments.
+ void TextBoxOnLostFocus(object? sender, FocusEventArgs e)
+ {
+ OnLostFocus();
+ SetValue(VisualElement.IsFocusedPropertyKey, e.IsFocused);
+ RaiseUnfocusedEvent(e);
+ UpdateClearButtonVisibility();
+ }
+
+
+ ///
+ /// Initialize the numeric entry
+ ///
+ void Initialize()
+ {
+ // Initialize resources for the numeric entry control.
+ SfNumericEntryResources.InitializeDefaultResource("Syncfusion.Maui.Toolkit.NumericEntry.Resources.SfNumericEntry");
+
+ // Set the drawing order to ensure key elements are drawn correctly
+ DrawingOrder = DrawingOrder.AboveContent;
+
+ // Add touch and keyboard listeners to the control
+ this.AddTouchListener(this);
+ this.AddKeyboardListener(this);
+
+ // Initialize and setup the text box entry view
+ _textBox = new SfEntryView();
+
+ // Initialize graphical and interactive elements of the control
+ InitializeElements();
+
+ // Add the text box to the control's visual tree
+ Add(_textBox);
+
+ // Determine if the description was set by the user, used for accessibility
+ _isDescriptionNotSetByUser = string.IsNullOrEmpty(SemanticProperties.GetDescription(this));
+ }
+
+ ///
+ /// Updates the clear button visibility based on control's state.
+ ///
+ void UpdateClearButtonVisibility()
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ if (ShowClearButton && _textBox.IsFocused && !_textBox.IsReadOnly
+ && !string.IsNullOrEmpty(_textBox.Text) && IsEnabled && IsEditable)
+ {
+ _isClearButtonVisible = true;
+ }
+ else
+ {
+ _isClearButtonVisible = false;
+ }
+ UpdateElementsBounds(_previousRectBounds);
+ }
+
+#if !ANDROID
+
+ ///
+ /// Validates the pasted text and formats it according to the number format of the control.
+ ///
+ /// The text input to validate and format.
+ /// Information on the numerical format to apply.
+ /// A formatted string.
+ static string? ValidatePastedText(string input, NumberFormatInfo numberFormatInfo)
+ {
+ string? result = string.Empty;
+ string decimalSeparator = GetNumberDecimalSeparator(numberFormatInfo);
+ string negativeSign = GetNegativeSign(numberFormatInfo);
+
+ // Removes the non-numeric characters except decimal separator and negative sign.
+ string pattern = @"[^\d" + decimalSeparator + negativeSign + "]+";
+ input = Regex.Replace(input, pattern, string.Empty);
+
+ foreach (char character in input)
+ {
+ // Ignores the duplicate decimal separator.
+ // Ignores the input containing negative sign after the first digit.
+ if ((character.ToString() == decimalSeparator && result.Contains(decimalSeparator, StringComparison.CurrentCulture)) ||
+ ((character.ToString() == negativeSign) && !string.IsNullOrEmpty(result)))
+ {
+ continue;
+ }
+
+ result += character;
+ }
+
+ result = double.TryParse(result, out _) || result == decimalSeparator ? result : null;
+ return result;
+ }
+#endif
+
+ ///
+ /// Gets the current culture number decimal separator.
+ ///
+ static string GetNumberDecimalSeparator(NumberFormatInfo numberFormatInfo)
+ {
+ return numberFormatInfo.NumberDecimalSeparator;
+ }
+
+ ///
+ /// Gets the current culture negative sign.
+ ///
+ static string GetNegativeSign(NumberFormatInfo numberFormatInfo)
+ {
+ return numberFormatInfo.NegativeSign;
+ }
+
+ ///
+ /// Checks whether zero is needed for the display text. Zero is needed when
+ /// display text is empty or no numbers before the decimal separator.
+ ///
+ bool PrefixZeroIfNeeded(ref string displayText, bool isNegative, NumberFormatInfo numberFormatInfo)
+ {
+ string negativeSign = GetNegativeSign(numberFormatInfo);
+ string decimalSeparator = GetNumberDecimalSeparator(numberFormatInfo);
+ displayText = Culture != null && Culture.TextInfo.IsRightToLeft && negativeSign.Length > 1 ? displayText.Trim(negativeSign[1]) : displayText.TrimStart(negativeSign[0]);
+
+ bool startsWithDecimal = displayText.StartsWith(decimalSeparator, StringComparison.CurrentCulture);
+ bool isZeroNeeded = startsWithDecimal || string.IsNullOrEmpty(displayText);
+ bool isCultureRightToLeftWithNegativeSign = Culture != null &&
+ Culture.TextInfo.IsRightToLeft &&
+ negativeSign.Length > 1 &&
+ displayText.Contains(negativeSign[1], StringComparison.Ordinal)? false : true;
+ if (isCultureRightToLeftWithNegativeSign)
+ {
+ displayText = isNegative ? (negativeSign + displayText) : displayText;
+ }
+ displayText = isZeroNeeded ? displayText.Insert(isNegative ? 1 : 0, "0") : displayText;
+
+ return isZeroNeeded;
+ }
+
+ ///
+ /// Removes selected text if it does not contain a decimal separator.
+ ///
+ static string RemoveSelectedText(string text, string selectedText, int caretPosition, NumberFormatInfo numberFormatInfo)
+ {
+ bool hasSelectedAll = text.Length == selectedText.Length;
+ bool selectedTextHasDecimal = selectedText.Contains(GetNumberDecimalSeparator(numberFormatInfo), StringComparison.CurrentCulture);
+ if (hasSelectedAll || !selectedTextHasDecimal)
+ {
+ text = text.Remove(caretPosition, selectedText.Length);
+ }
+#if WINDOWS
+ else if ((text.Length == selectedText.Length + 1) && !selectedText.Contains('-', StringComparison.Ordinal) && text.Contains('-', StringComparison.Ordinal))
+ {
+ text = text.Remove(0, selectedText.Length + 1);
+ }
+#endif
+ return text;
+ }
+
+ ///
+ /// Checks whether the given value is negative or not.
+ ///
+ static bool IsNegative(string text, NumberFormatInfo numberFormatInfo)
+ {
+ return text.StartsWith(GetNegativeSign(numberFormatInfo), StringComparison.CurrentCulture);
+ }
+
+ ///
+ /// Update the textbox visibility based on property.
+ ///
+ void UpdateEntryVisibility()
+ {
+ if (_textBox != null)
+ {
+ _textBox.IsVisible = true;
+ }
+ }
+
+ ///
+ /// To get the NumberFormat from number formatter, culture property or current UI culture.
+ ///
+ NumberFormatInfo GetNumberFormat()
+ {
+ // Determine the culture to use for number formatting.
+ CultureInfo selectedCulture = CustomFormat != null ?
+ (Culture ?? CultureInfo.CurrentUICulture) :
+ (Culture ?? CultureInfo.CurrentUICulture);
+
+ // Return the NumberFormatInfo of the selected culture.
+ return selectedCulture.NumberFormat;
+ }
+
+ ///
+ /// Handles the event when the IsEnabled property changes.
+ ///
+ /// The source of the event.
+ /// An EventArgs that contains the event data.
+ void OnIsEnabledChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ UpdateVisualState();
+ }
+
+#if WINDOWS
+ ///
+ /// Helps to convert the virtual key to the Keyboard Key.
+ ///
+ KeyboardKey ConvertToKeyboardKey(VirtualKey virtualKey)
+ {
+ KeyboardKey key = KeyboardKey.None;
+
+ switch (virtualKey)
+ {
+ case VirtualKey.Left:
+ key = KeyboardKey.Left;
+ break;
+ case VirtualKey.Right:
+ key = KeyboardKey.Right;
+ break;
+ case VirtualKey.Up:
+ key = KeyboardKey.Up;
+ break;
+ case VirtualKey.Down:
+ key = KeyboardKey.Down;
+ break;
+ case VirtualKey.PageUp:
+ key = KeyboardKey.PageUp;
+ break;
+ case VirtualKey.PageDown:
+ key = KeyboardKey.PageDown;
+ break;
+ case VirtualKey.Home:
+ key = KeyboardKey.Home;
+ break;
+ }
+
+ return key;
+ }
+#endif
+
+#if WINDOWS
+ ///
+ /// Occurs when key is pressed.
+ ///
+ /// The NumberBox control.
+ /// The DependencyPropertyChanged EventArgs.
+ internal void OnTextBoxPreviewKeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
+ {
+ if (_textBox != null)
+ {
+ if (_textBox.IsReadOnly && e.Key != VirtualKey.Up && e.Key != VirtualKey.Down
+ && e.Key != VirtualKey.PageUp && e.Key != VirtualKey.PageDown)
+ {
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(_textBox.Text) && _textBox.Text.Length < _textBox.CursorPosition)
+ {
+ _textBox.CursorPosition = _textBox.Text.Length;
+ }
+ int caretPosition = _textBox.CursorPosition;
+ KeyboardKey key = ConvertToKeyboardKey(e.Key);
+ string text = e.Key.ToString();
+ bool isNegative = !string.IsNullOrEmpty(_textBox.Text) && IsNegative(_textBox.Text, GetNumberFormat());
+ bool isCtrlKeyPressed = GetVirtualKeyStates(VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
+ bool isAltKeyPressed = GetVirtualKeyStates(VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
+ bool isShiftKeyPressed = GetVirtualKeyStates(VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ bool areModifierKeysPressed = isCtrlKeyPressed || isAltKeyPressed || isShiftKeyPressed;
+
+ if (!areModifierKeysPressed && text.Contains("Number", StringComparison.CurrentCulture))
+ {
+ // Handles when the number is pressed.
+ e.Handled = true;
+ HandleNumbers(text, caretPosition, decimalSeparator);
+ }
+
+ else if (!areModifierKeysPressed && (e.Key == VirtualKey.Decimal || (int)e.Key == 190 && decimalSeparator == "." || (int)e.Key == 188 && decimalSeparator == ","))
+ {
+ // Handles when the pressed key is decimal and current culture decimal separator. Suppose if we type decimal in french culture, we need to
+ // insert the decimal separator based on french culture.
+ e.Handled = true;
+
+ //Allow to enter the decimal separator when maximum fraction digit is not 0.
+ if ((_maximumPositiveFractionDigit != 0 && !_textBox.Text.Contains('-', StringComparison.Ordinal)) || (_maximumNegativeFractionDigit != 0 && _textBox.Text.Contains('-', StringComparison.Ordinal)))
+ {
+ HandleDecimalSeparator(decimalSeparator, caretPosition);
+ }
+ }
+ else if (e.Key == VirtualKey.Back)
+ {
+ // Handles when the backspace key is pressed.
+ e.Handled = true;
+ HandleBackspace(caretPosition, decimalSeparator);
+ }
+ else if (!isShiftKeyPressed && e.Key == VirtualKey.Delete)
+ {
+ // Handles when the delete key is pressed
+ e.Handled = true;
+ HandleDelete(caretPosition, decimalSeparator);
+ }
+ else if ((int)e.Key == 189 || e.Key == VirtualKey.Subtract)
+ {
+ // Handles when the negative key is pressed.
+ e.Handled = true;
+ HandleNegativeKey(isNegative, caretPosition);
+ }
+ else if (e.Key == VirtualKey.Left || e.Key == VirtualKey.Right || e.Key == VirtualKey.Home ||
+ e.Key == VirtualKey.End || e.Key == VirtualKey.Up || e.Key == VirtualKey.Down ||
+ e.Key == VirtualKey.PageUp || e.Key == VirtualKey.PageDown)
+ {
+ // Handles when the arrow key or page up/down key is pressed.
+ e.Handled = HandleNavigation(key, isNegative, caretPosition);
+ }
+ else if (e.Key == VirtualKey.Enter)
+ {
+ // Handles when enter key is pressed.
+ UpdateValue();
+ e.Handled = false;
+ }
+ else if (e.Key == VirtualKey.Escape)
+ {
+ // Handles when escape key is pressed and sets the last value to the textbox.
+ UpdateDisplayText(Value, resetCursor: false);
+ e.Handled = false;
+ }
+ else if (isCtrlKeyPressed && e.Key == VirtualKey.Z)
+ {
+ // Performs undo operation.
+ var oldText = _textBox.Text;
+ UpdateDisplayText(Value);
+ _redoText = (oldText != _textBox.Text) ? oldText : _redoText;
+ e.Handled = true;
+ }
+ else if (isCtrlKeyPressed && e.Key == VirtualKey.Y)
+ {
+ // Performs redo operation.
+ e.Handled = true;
+ if (_redoText != null)
+ {
+ var redoValue = Parse(_redoText);
+ UpdateDisplayText(redoValue);
+ _redoText = null;
+ }
+ }
+ else if ((isCtrlKeyPressed && (e.Key == VirtualKey.C || e.Key == VirtualKey.X || e.Key == VirtualKey.Insert
+ || e.Key == VirtualKey.V || e.Key == VirtualKey.A)) || (!isCtrlKeyPressed && e.Key == VirtualKey.Tab)
+ || (isShiftKeyPressed && (e.Key == VirtualKey.Delete || e.Key == VirtualKey.Insert)))
+ {
+ // Cut, Copy, Paste, and Tab key operations are performed by TextBox control.
+ e.Handled = false;
+ }
+ else
+ {
+ // Do nothing.
+ e.Handled = true;
+ }
+ }
+ }
+#endif
+
+#if WINDOWS
+ ///
+ /// Occurs when text is pasted in TextBox.
+ ///
+ /// NumberBox control.
+ /// TextControlPasteEventArgs.
+ async void OnTextBoxPaste(object sender, Microsoft.UI.Xaml.Controls.TextControlPasteEventArgs e)
+ {
+ // Ensure paste handling is consistent with custom logic
+ e.Handled = true;
+ if (sender is Microsoft.UI.Xaml.Controls.TextBox textBox)
+ {
+ var clipboardContent = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
+
+ if (clipboardContent.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text))
+ {
+ try
+ {
+ var copiedText = await clipboardContent.GetTextAsync();
+ copiedText = ValidatePastedText(copiedText, GetNumberFormat());
+
+ if (!string.IsNullOrEmpty(copiedText) && CanPaste(copiedText))
+ {
+ InsertNumbers(
+ textBox.Text,
+ textBox.SelectedText,
+ copiedText,
+ textBox.SelectionStart
+ );
+ }
+ }
+ catch (Exception ex)
+ {
+ // Consider logging or handling the error as per your needs
+ throw new InvalidOperationException("An error occurred while processing the clipboard text.", ex);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Occurs before cut text is moved to the clipboard.
+ ///
+ void OnTextBoxCuttingToClipboard(TextBox sender, TextControlCuttingToClipboardEventArgs args)
+ {
+ // Prevent the default cut operation to implement custom logic
+ args.Handled = true;
+
+ if (sender is null)
+ {
+ return;
+ }
+
+ var selectedText = sender.SelectedText;
+ var caretPosition = sender.SelectionStart;
+ var displayText = sender.Text;
+
+ // Ensure there is text selected before modifying the TextBox
+ if (selectedText.Length > 0)
+ {
+ sender.CopySelectionToClipboard();
+
+ displayText = RemoveSelectedText(displayText, selectedText, caretPosition, GetNumberFormat());
+ PrefixZeroAndUpdateCursor(ref displayText, ref caretPosition);
+
+ // Update TextBox properties based on changes made to ensure coherent UI state
+ sender.Text = displayText;
+ sender.SelectionLength = 0;
+ sender.SelectionStart = caretPosition;
+ }
+ }
+
+ ///
+ /// Occurs when the text selection starts to change.
+ ///
+ void OnTextBoxSelectionChanging(TextBox sender, TextBoxSelectionChangingEventArgs args)
+ {
+ // Should not allow the cursor before the negative sign.
+ // Allowing keyboard input before negative sign, gives invalid input.
+ // | -> cursor position
+ // Eg: |-3.12 -> 1|-3.12 -> invalid input.
+ if (_textBox != null && IsNegative(_textBox.Text, GetNumberFormat()) && args.SelectionLength == 0 && args.SelectionStart == 0)
+ {
+ args.Cancel = true;
+ _textBox.CursorPosition = 1;
+ }
+ }
+#endif
+
+ ///
+ /// Checks whether to allow perform paste operation or not.
+ ///
+ bool CanPaste(string copiedText)
+ {
+ bool allowPaste = false;
+ if (copiedText != null && _textBox != null)
+ {
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ bool copiedTextHasDecimal = copiedText.Contains(decimalSeparator, StringComparison.CurrentCulture);
+ bool hasSelectedAll = _textBox.Text.Length == _textBox.SelectionLength;
+ bool selectedTextHasDecimal = _textBox.Text.Substring(_textBox.CursorPosition, _textBox.SelectionLength).Contains(decimalSeparator, StringComparison.CurrentCulture);
+ bool textHasDecimal = _textBox.Text.Contains(decimalSeparator, StringComparison.CurrentCulture);
+
+ if (hasSelectedAll || (!copiedTextHasDecimal && !selectedTextHasDecimal) ||
+ (copiedTextHasDecimal && (selectedTextHasDecimal || !textHasDecimal)))
+ {
+ allowPaste = true;
+ }
+ }
+
+ return allowPaste;
+ }
+
+ ///
+ /// Setup logic when clearing of text is requested by a user.
+ ///
+ void ClearButtonOnClick(object sender, EventArgs e)
+ {
+ UpdateDisplayText(null);
+ }
+
+ ///
+ /// Occurs when control is loaded.
+ ///
+ /// NumberBox control.
+ /// RoutedEventArgs.
+ void OnLoaded(object? sender, EventArgs e)
+ {
+ FormatValue();
+#if WINDOWS
+ GetParentElement();
+ if (IsRTL())
+ {
+ SetFlowDirection();
+ }
+#endif
+
+ }
+
+ ///
+ /// Formats the current value of the numeric entry based on the defined custom format or maximum decimal digits.
+ ///
+ void FormatValue(bool isValueChange = false)
+ {
+ // Only format the value when the control is not focused
+ if (_textBox == null || _textBox.IsFocused)
+ {
+ return;
+ }
+
+ if (Value == null || double.IsNaN(Value.Value))
+ {
+ return;
+ }
+
+ string formattedValue;
+ var numberFormat = GetNumberFormat();
+
+ if (CustomFormat != null)
+ {
+ bool containsPercentageSymbol = CustomFormat.Contains('p', StringComparison.OrdinalIgnoreCase);
+ if(_maximumPositiveFractionDigit >=0 && !isValueChange && _maximumPositiveFractionDigit < 16)
+ {
+ Value = (double)Math.Round((double)Value, _maximumPositiveFractionDigit);
+ }
+ // Apply percentage conversion if PercentDisplayMode demands it.
+ double valueToFormat = containsPercentageSymbol && PercentDisplayMode == PercentDisplayMode.Value
+ ? Value.Value / 100
+ : Value.Value;
+
+ formattedValue = valueToFormat.ToString(CustomFormat, numberFormat);
+ }
+ else
+ {
+ int maximumDecimalDigits = MaximumNumberDecimalDigits < 0 ? 0 : MaximumNumberDecimalDigits;
+ if(MaximumNumberDecimalDigits >=0 && !isValueChange && MaximumNumberDecimalDigits < 16)
+ {
+ Value = (double)Math.Round((double)Value, maximumDecimalDigits);
+ }
+ formattedValue = Value.Value.ToString("N" + maximumDecimalDigits, numberFormat);
+ }
+
+ _textBox.Text = formattedValue;
+ }
+
+ ///
+ /// Inserts the number on the current cursor position or over the selected text.
+ ///
+ void HandleNumbers(string key, int caretPosition, string decimalSeparator)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ // Extract the last character from the key input which represents the digit.
+ char input = key.Last();
+ string displayText = GetDisplayTextForPlatform(caretPosition, GetSelectionLength());
+ string selectedText = GetSelectedText(caretPosition);
+
+ // Check if the entire text or selected part includes a decimal to control fraction input.
+ bool hasSelectedAll = selectedText.Equals(displayText, StringComparison.Ordinal);
+ bool selectedTextHasDecimal = selectedText.Contains(decimalSeparator, StringComparison.CurrentCulture);
+#if IOS
+ // On iOS, the selectedText is empty, but it incorrectly returns true when checking if it contains the decimalSeparator.
+ // Added a condition to set selectedTextHasDecimal to false if selectedText is empty.
+ if (string.IsNullOrEmpty(selectedText))
+ {
+ selectedTextHasDecimal = false;
+ }
+#endif
+ // Proceed with number insertion only if the selected text doesn't have a decimal or the entire text is selected.
+ if (!selectedTextHasDecimal || hasSelectedAll)
+ {
+ InsertNumbers(displayText, selectedText, input.ToString(), caretPosition);
+ }
+ }
+
+ ///
+ /// If the text is not selected, removes the left character of the current cursor position
+ /// If text is selected, removes the selected text.
+ ///
+ void HandleBackspace(int caretPosition, string decimalSeparator)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ string displayText = _textBox.Text;
+ string selectedText = GetSelectedOrAdjacentText(caretPosition);
+
+ bool hasSelectedAll = displayText.Equals(selectedText, StringComparison.Ordinal);
+ var isTextParsed = double.TryParse(displayText, out double value);
+
+ if (selectedText.Length > 0)
+ {
+ value = hasSelectedAll ? 0 : value;
+#if MACCATALYST || IOS
+ displayText = RemoveSelectedText(displayText, selectedText, caretPosition-1, GetNumberFormat());
+#else
+ displayText = RemoveSelectedText(displayText, selectedText, caretPosition, GetNumberFormat());
+#endif
+ }
+ else if (caretPosition > 0 && displayText.Length > 0)
+ {
+ displayText = displayText.Remove(caretPosition - 1, 1);
+ }
+
+ PrefixZeroAndUpdateCursor(ref displayText, ref caretPosition, isBackspace: true, value);
+
+ UpdateTextBoxAndCursorPosition(displayText, caretPosition);
+ }
+
+ ///
+ /// Retrieves either the selected text or the text to the left of the caret position.
+ ///
+ string GetSelectedOrAdjacentText(int caretPosition)
+ {
+ if (_textBox == null)
+ {
+ return "0";
+ }
+
+ #if MACCATALYST || IOS
+ return _textBox.Text.Substring(caretPosition - 1, _textBox.SelectionLength);
+ #else
+ return _textBox.Text.Substring(_textBox.CursorPosition, _textBox.SelectionLength);
+ #endif
+ }
+
+ ///
+ /// Updates the text box content and cursor position after processing backspace logic.
+ ///
+ void UpdateTextBoxAndCursorPosition(string displayText, int caretPosition)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+#if (MACCATALYST || IOS)
+ Task.Run(() =>
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ _textBox.Text = displayText;
+ _textBox.CursorPosition = Math.Max(0, caretPosition);
+ });
+ });
+#else
+ _textBox.Text = displayText;
+#endif
+ _textBox.CursorPosition = Math.Max(0, caretPosition);
+ }
+
+ ///
+ /// Removes the right character of the current cursor position
+ /// if the text is not selected, or removes the selected text.
+ ///
+ void HandleDelete(int caretPosition, string decimalSeparator)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ string displayText = _textBox.Text;
+ string selectedText = GetSelectedText(caretPosition);
+
+ if (selectedText.Length > 0)
+ {
+ // Remove selected text
+ displayText = RemoveSelectedText(displayText, selectedText, caretPosition, GetNumberFormat());
+ }
+ else if (caretPosition >= 0 && caretPosition < displayText.Length)
+ {
+ // Remove character to the right of caret, skipping the decimal separator.
+ if (displayText[caretPosition].ToString() != decimalSeparator)
+ {
+ displayText = displayText.Remove(caretPosition, 1);
+ }
+ else
+ {
+ caretPosition++; // Skip the decimal separator
+ }
+ }
+
+ PrefixZeroAndUpdateCursor(ref displayText, ref caretPosition, isBackspace: false);
+ UpdateTextBoxAndCursorPosition(displayText, caretPosition);
+ }
+
+ ///
+ /// Performs arrow key navigation or updates the value by small/large change.
+ ///
+ bool HandleNavigation(KeyboardKey key, bool isNegative, int caretPosition)
+ {
+ bool isHandled = false;
+ if (key == KeyboardKey.Home && isNegative && _textBox != null)
+ {
+ // Should not allow the cursor before the negative sign.
+ _textBox.CursorPosition = 1;
+ _textBox.SelectionLength = 0;
+ isHandled = true;
+ }
+ else if (key == KeyboardKey.Up || key == KeyboardKey.Down)
+ {
+ isHandled = HandleKeyPressed(key);
+
+ }
+ else if (key == KeyboardKey.PageUp || key == KeyboardKey.PageDown)
+ {
+ isHandled = HandleKeyPagePressed(key);
+ }
+ else if (key == KeyboardKey.Left && isNegative && caretPosition == 1 && _textBox != null)
+ {
+ // If text is selected then it should be cleared.
+ _textBox.SelectionLength = 0;
+ isHandled = true;
+ }
+
+ return isHandled;
+ }
+
+ ///
+ /// Gets the current display text based on the platform.
+ ///
+ /// The current display text or an empty string if not available.
+ string GetDisplayText()
+ {
+#if WINDOWS
+ if (_textBox is null)
+ {
+ return string.Empty;
+ }
+
+ return _textBox.Text;
+#else
+ return _previousText;
+#endif
+ }
+
+ ///
+ /// Gets the currently selected text based on the platform and caret position.
+ ///
+ /// The current caret position in the text.
+ /// The selected text or an empty string if no selection.
+ string GetSelectedText(int caretPosition)
+ {
+#if WINDOWS
+ if (_textBox != null && _textBox.Text != null)
+ {
+ return _textBox.Text.Substring(_textBox.CursorPosition, _textBox.SelectionLength);
+ }
+ return string.Empty;
+#elif ANDROID
+ return _previousText != null ? _previousText.Substring(caretPosition, _selectionLength) : string.Empty;
+#elif MACCATALYST || IOS
+ return _textBox != null && _previousText != null ? _previousText.Substring(caretPosition, _textBox.SelectionLength) : string.Empty;
+#else
+ return string.Empty;
+#endif
+ }
+
+ ///
+ /// Processes the display text for the current platform, applying platform-specific logic.
+ ///
+ /// The text to be displayed, passed by reference.
+ void ProcessDisplayTextForPlatform(ref string displayText)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ #if ANDROID
+ // Prefix zero if needed, except on Samsung devices
+ if (!IsSamsungDevice())
+ {
+ PrefixZeroIfNeeded(ref displayText, IsNegative(displayText, GetNumberFormat()), GetNumberFormat());
+ }
+ #elif MACCATALYST || IOS
+ // On iOS platforms, ensure valid text before assigning
+ if (_isNotValidText)
+ {
+ return;
+ }
+ #endif
+
+ // Update the text box for supported platforms
+ _textBox.Text = displayText;
+ }
+
+ ///
+ /// Inserts decimal separator if text not contains the decimal separator or
+ /// move the cursor after the decimal separator.
+ ///
+ void HandleDecimalSeparator(string decimalSeparator, int caretPosition)
+ {
+ if (_textBox != null)
+ {
+ string displayText = GetDisplayText();
+ string selectedText = GetSelectedText(caretPosition);
+
+ bool displayTextHasDecimal = displayText.Contains(GetNumberDecimalSeparator(GetNumberFormat()), StringComparison.CurrentCulture);
+ bool selectedTextHasDecimal = selectedText.Contains(GetNumberDecimalSeparator(GetNumberFormat()), StringComparison.CurrentCulture);
+#if IOS
+ // On iOS, the selectedText is empty, but it incorrectly returns true when checking if it contains the decimalSeparator.
+ // Added a condition to set selectedTextHasDecimal to false if selectedText is empty.
+ if (string.IsNullOrEmpty(selectedText))
+ {
+ selectedTextHasDecimal = false;
+ }
+#endif
+ if (!displayTextHasDecimal || selectedTextHasDecimal)
+ {
+ displayText = displayText.Remove(caretPosition, selectedText.Length);
+ displayText = displayText.Insert(caretPosition, decimalSeparator);
+ ProcessDisplayTextForPlatform(ref displayText);
+ }
+
+ ProcessCursorPosition(decimalSeparator, caretPosition, displayText);
+ }
+ }
+
+ ///
+ /// Processes the cursor position based on the decimal separator, caret position, and display text.
+ ///
+ /// The decimal separator character.
+ /// The current caret position.
+ /// The text being displayed.
+ void ProcessCursorPosition(string decimalSeparator, int caretPosition, string displayText)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+#if !WINDOWS
+ if (!displayText.Equals(decimalSeparator, StringComparison.Ordinal) && !displayText.StartsWith(decimalSeparator))
+ {
+#if ANDROID
+ _textBox.Text = displayText;
+#elif MACCATALYST || IOS
+ if (!_isNotValidText)
+ {
+ _textBox.Text = displayText;
+ }
+#endif
+ }
+#endif
+ if (ShouldUpdateCursorPosition())
+ {
+ int decimalIndex = _textBox.Text.IndexOf(decimalSeparator, StringComparison.CurrentCulture);
+ UpdateCursorPosition(decimalIndex, caretPosition, displayText, decimalSeparator);
+ }
+ }
+
+ ///
+ /// Determines whether the cursor position should be updated.
+ ///
+ /// True if the cursor position should be updated; otherwise, false.
+ bool ShouldUpdateCursorPosition()
+ {
+ if (_textBox == null)
+ {
+ return false;
+ }
+#if ANDROID
+ return _selectionLength >= 0;
+#elif WINDOWS
+ return _textBox.SelectionLength >= 0;
+#else
+ return _textBox.SelectionLength == 0;
+#endif
+ }
+
+ ///
+ /// Updates the cursor position based on the decimal index and platform-specific conditions.
+ ///
+ /// The index of the decimal separator.
+ /// The current caret position.
+ /// The text being displayed.
+ /// The decimal separator character.
+ void UpdateCursorPosition(int decimalIndex, int caretPosition, string displayText, string decimalSeparator)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+#if ANDROID
+ if (!IsSamsungDevice())
+ {
+ _cursorPosition = decimalIndex + 1;
+ }
+ else
+ {
+ if (caretPosition == 0 && displayText != null && displayText.Contains(decimalSeparator, StringComparison.Ordinal))
+ {
+ HandleNegativeKey(false, 0);
+ }
+ else
+ {
+ _cursorPosition = decimalIndex + 1;
+ }
+ }
+#else
+ _textBox.CursorPosition = decimalIndex + 1;
+#endif
+ }
+
+ ///
+ /// Gets the length of the current text selection.
+ ///
+ /// The length of the selected text.
+ int GetSelectionLength()
+ {
+#if ANDROID
+ return _selectionLength;
+#else
+ if (_textBox is null)
+ {
+ return 0;
+ }
+ return _textBox.SelectionLength;
+#endif
+ }
+
+ ///
+ /// Gets the display text for the current platform.
+ ///
+ /// The current caret position.
+ /// The length of the current selection.
+ /// The current text for display.
+ string GetDisplayTextForPlatform(int caretPosition, int selectionLength)
+ {
+#if WINDOWS
+ if (_textBox is null)
+ {
+ return string.Empty;
+ }
+ return _textBox.Text;
+#else
+ return _previousText;
+#endif
+ }
+
+ ///
+ /// Conditionally appends a negative sign to current input if needed or removes it.
+ ///
+ void HandleNegativeKey(bool isNegative, int caretPosition)
+ {
+ if (_textBox != null)
+ {
+ int selectionLength = GetSelectionLength();
+#if WINDOWS
+ string displayText = _textBox.Text;
+ if (displayText == _textBox.Text.Substring(_textBox.CursorPosition, _textBox.SelectionLength))
+#elif !WINDOWS
+ string displayText = _previousText;
+#if ANDROID
+ _selectionLength = selectionLength;
+ if (displayText == _previousText.Substring(caretPosition, selectionLength))
+#else
+ if (displayText == _previousText.Substring(caretPosition, _textBox.SelectionLength))
+#endif
+
+#endif
+ {
+ // Appends zero when the negative key is pressed and the text is empty or selected all.
+ displayText = displayText.Remove(caretPosition, selectionLength);
+ displayText = displayText.Insert(0, GetNegativeSign(GetNumberFormat()));
+ PrefixZeroIfNeeded(ref displayText, isNegative: true, GetNumberFormat());
+ _textBox.Text = displayText;
+ _textBox.CursorPosition = caretPosition + 2;
+#if ANDROID
+ _cursorPosition = caretPosition + 2;
+#endif
+ }
+#if ANDROID
+ else if (displayText.StartsWith('.') && IsSamsungDevice())
+ {
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ displayText = string.Concat("", displayText.AsSpan(1));
+ _textBox.Text = displayText;
+ _cursorPosition = 1;
+ }
+#endif
+ else
+ {
+ // Adds a negative sign if it is not available, or removes the negative sign.
+ _textBox.Text = isNegative ? displayText.Remove(0, 1) :
+ displayText.Insert(0, GetNegativeSign(GetNumberFormat()));
+#if !WINDOWS
+ _textBox.CursorPosition = caretPosition != 0 ? (caretPosition + (isNegative ? -1 : 1)) : caretPosition;
+#if ANDROID
+ _cursorPosition = caretPosition != 0 ? (caretPosition + (isNegative ? -1 : 1)) : caretPosition;
+#endif
+#elif WINDOWS
+ _textBox.CursorPosition = caretPosition + (isNegative ? -1 : 1);
+#endif
+ _textBox.SelectionLength = selectionLength;
+ }
+ }
+ }
+
+ ///
+ /// To get the maximum fraction digits from valid custom format.
+ ///
+ /// The .
+ /// the maximum fraction digits
+ int GetMaximumFractionDigitFromValidFormat(string customFormat)
+ {
+ int maxFractionDigit = -1;
+ string validFormat = string.Empty;
+
+ //To remove escape characters and other symbols from custom format.
+ for (int i = 0; i < customFormat.Length; i++)
+ {
+ char formatCharecter = customFormat[i];
+ if (formatCharecter != '\\')
+ {
+ if (formatCharecter == '0' || formatCharecter == '#' || (formatCharecter == '.' && !validFormat.Contains('.', StringComparison.Ordinal)))
+ {
+ validFormat += formatCharecter;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ //To get the maximum fraction digit from the valid format.
+ int dotIndex = validFormat.IndexOf('.', StringComparison.Ordinal);
+ bool hasMoreThanOneCharacterAfterDot = dotIndex >= 0 && validFormat.Length - dotIndex > 1;
+ if (validFormat.Contains('.', StringComparison.Ordinal) && hasMoreThanOneCharacterAfterDot)
+ {
+ maxFractionDigit = (dotIndex >= 0) ? (validFormat.Length - dotIndex - 1) : 0;
+ }
+ else if (!string.IsNullOrEmpty(validFormat))
+ {
+ maxFractionDigit = 0;
+ }
+
+ return maxFractionDigit;
+ }
+
+ ///
+ /// Determines if the given character represents a standard numeric format specifier.
+ ///
+ bool IsStandardFormat(char formatChar)
+ {
+ return formatChar == 'C' || formatChar == 'E' || formatChar == 'F' ||
+ formatChar == 'N' || formatChar == 'P' || formatChar == 'G' || formatChar == 'R';
+ }
+
+ ///
+ /// Returns the default number of fraction digits for a given numeric format specifier.
+ ///
+ int GetDefaultFractionDigitsForFormat(char formatChar)
+ {
+ if (formatChar == 'G' || formatChar == 'R')
+ {
+ return 15; // Default maximum fraction digits for 'G' and 'R'
+ }
+
+ return (formatChar != 'E') ? 2 : -1; // Default for others except 'E'
+ }
+
+ ///
+ /// Gets the maximum fraction digits from the custom format.
+ ///
+ /// The custom number format.
+ /// The maximum fraction digits allowed by the format.
+ int GetMaximumFractionDigits(string customFormat)
+ {
+ int maxFractionDigit = -1;
+ char customFormatFirstChar = char.ToUpper(Convert.ToChar(customFormat[0]));
+
+ //To get the maximum fraction digit from standard custom formats.
+ if (IsStandardFormat(customFormatFirstChar))
+ {
+ if (customFormat.Length == 1)
+ {
+ GetDefaultFractionDigitsForFormat(customFormatFirstChar);
+ }
+ else
+ {
+ string maxFractionFormat = customFormat.Remove(0, 1);
+
+ //To get the assigned maximum fraction digit for the standard format.(ex:'C10' --> Maximum fraction digits is 10.)
+ if (maxFractionFormat.Length <= 2)
+ {
+ if (customFormatFirstChar != 'E')
+ {
+ string maxDigit = string.Empty;
+ foreach (char formatDigit in maxFractionFormat)
+ {
+ if (formatDigit >= '0' && formatDigit <= '9')
+ {
+ maxDigit += formatDigit.ToString();
+ }
+ else
+ {
+ //To get the maximum fraction digit for the normal custom formats.(ex:'C#.0' --> Maximum fraction digits is 1.)
+ maxFractionDigit = GetMaximumFractionDigitFromValidFormat(maxFractionFormat);
+ maxDigit = string.Empty;
+ break;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(maxDigit))
+ {
+ maxFractionDigit = int.Parse(maxDigit) > 99 ? -1 : int.Parse(maxDigit);
+ }
+ }
+ }
+ else
+ {
+ //To get the maximum fraction digit from normal custom formats.(ex:'C#.00##' --> Maximum fraction digits is 4.)
+ maxFractionDigit = GetMaximumFractionDigitFromValidFormat(maxFractionFormat);
+ }
+ }
+ }
+ else
+ {
+ //To get the maximum fraction digit from common custom formats.
+ maxFractionDigit = GetMaximumFractionDigitFromValidFormat(customFormat);
+ }
+
+ return maxFractionDigit;
+ }
+
+ ///
+ /// Updates maximum fraction digit properties based on custom formatting or defaults.
+ ///
+ void UpdateMaximumFractionDigit()
+ {
+ if (CustomFormat == null)
+ {
+ _maximumPositiveFractionDigit = MaximumNumberDecimalDigits < 0 ? 0 : MaximumNumberDecimalDigits;
+ _maximumNegativeFractionDigit = MaximumNumberDecimalDigits < 0 ? 0 : MaximumNumberDecimalDigits;
+ }
+ else if (!string.IsNullOrEmpty(CustomFormat))
+ {
+ //To get the positive and negative custom formats.
+ string[] customFormats = CustomFormat.Split(';');
+
+ if (customFormats.Length == 1)
+ {
+ _maximumPositiveFractionDigit = GetMaximumFractionDigits(customFormats[0]);
+ _maximumNegativeFractionDigit = GetMaximumFractionDigits(customFormats[0]);
+ }
+ else if (customFormats.Length > 1)
+ {
+ _maximumPositiveFractionDigit = GetMaximumFractionDigits(customFormats[0]);
+ _maximumNegativeFractionDigit = GetMaximumFractionDigits(customFormats[1]);
+ }
+ }
+ }
+
+ ///
+ /// Restricts digits in the fractional part of the number in the .
+ ///
+ /// The current text displayed.
+ /// The result after applying fraction digit restrictions.
+ internal string RestrictFractionDigit(string displayText)
+ {
+ string numberDecimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+ if (!string.IsNullOrEmpty(displayText))
+ {
+ int fractionDigitCount = 0;
+ int maxDigitCount;
+
+ //To get the fraction digits in the display text.
+ if (displayText.Contains(numberDecimalSeparator, StringComparison.Ordinal))
+ {
+ int separatorIndex = displayText.IndexOf(numberDecimalSeparator);
+ fractionDigitCount = (separatorIndex >= 0) ? (displayText.Length - separatorIndex - 1) : 0;
+ }
+
+ //To get the maximum fraction digits from NumberFormatter and CustomFormat.
+ if (IsNegative(displayText, GetNumberFormat()) && CustomFormat != null)
+ {
+ //Assigning the negative format's maximum fraction digit.
+ maxDigitCount = _maximumNegativeFractionDigit;
+ }
+ else
+ {
+ //Assigning the positive format's maximum fraction digit.
+ maxDigitCount = _maximumPositiveFractionDigit;
+ }
+
+ //To remove the extra fraction digit from the display text.
+ if (maxDigitCount > -1 && fractionDigitCount > maxDigitCount)
+ {
+ int decimalSeparatorIndex = displayText.IndexOf(numberDecimalSeparator);
+ if (maxDigitCount == 0)
+ {
+ decimalSeparatorIndex += maxDigitCount;
+ }
+ else
+ {
+ decimalSeparatorIndex += maxDigitCount + 1;
+ }
+ displayText = displayText.Remove(decimalSeparatorIndex);
+ }
+ }
+ return displayText;
+ }
+
+ ///
+ /// Updates the text box with new display text and caret position.
+ ///
+ /// The new display text for the text box.
+ /// The new caret position after the update.
+ void UpdateTextBox(string displayText, int caretPosition)
+ {
+ if (_textBox != null)
+ {
+#if !(MACCATALYST || IOS)
+ UpdateTextBoxCursorPosition(displayText,caretPosition);
+#else
+ // If the cursor position is in middle then the position not updated properly
+ if(_textBox.Text.Length != _textBox.CursorPosition)
+ {
+ Task.Run(() =>
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ UpdateTextBoxCursorPosition(displayText,caretPosition);
+ });
+ });
+ }
+ else
+ {
+ UpdateTextBoxCursorPosition(displayText,caretPosition);
+ }
+
+ // Added to resolve cursor positioning issue after reaching Maximum.
+ bool isValid = double.TryParse(_textBox.Text, out double result);
+ if (Maximum != result)
+ {
+ _textBox.CursorPosition = caretPosition >= 0 ? (caretPosition <= displayText.Length) ? caretPosition : displayText.Length : 0;
+ }
+#endif
+
+#if ANDROID
+ _cursorPosition = caretPosition >= 0 ? caretPosition : 0;
+#endif
+ _textBox.SelectionLength = 0;
+ }
+ }
+
+ ///
+ /// Update the cursor position .
+ ///
+ private void UpdateTextBoxCursorPosition(string displayText, int caretPosition)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ _textBox.Text = displayText;
+ if (_textBox.Text != null)
+ {
+ _textBox.CursorPosition = caretPosition >= 0 ? (caretPosition <= _textBox.Text.Length) ? caretPosition : _textBox.Text.Length : 0;
+ }
+ else
+ {
+ _textBox.CursorPosition = 0;
+ }
+ }
+
+ ///
+ /// Inserts numbers at the specified position in the .
+ ///
+ /// Current display text.
+ /// Text that is currently selected.
+ /// The input to insert.
+ /// The position of the caret where input should be inserted.
+ void InsertNumbers(string displayText, string selectedText, string input, int caretPosition)
+ {
+ displayText = displayText.Remove(caretPosition, Math.Min(selectedText.Length, displayText.Length - caretPosition));
+ string negativeSign = GetNegativeSign(GetNumberFormat());
+ bool isNegative = IsNegative(displayText, GetNumberFormat());
+
+ // Removes the negative sign from input to avoid conflict if display text has a negative sign.
+ input = isNegative ? input.Replace(negativeSign, string.Empty, StringComparison.CurrentCulture) : input;
+ displayText = displayText.Insert(caretPosition, input);
+ displayText = RestrictFractionDigit(displayText);
+ isNegative = isNegative || IsNegative(input, GetNumberFormat());
+ int textLength = displayText.Length;
+ AdjustDisplayText(ref displayText, negativeSign, isNegative);
+
+ // Updates the cursor index to next to the given input by considering the duplicate digits from input text.
+ // For example, The display text is 1.45, the cursor index is 0, the selected text is 1,
+ // and the input text is 0000. Now, the result is 0.45, and the cursor index is 1.
+ caretPosition += input.Length - (textLength - displayText.Length);
+
+ UpdateTextBox(displayText, caretPosition);
+ }
+
+ ///
+ /// Adjusts the display text by removing leading zeroes and appending necessary prefixes.
+ ///
+ /// The display text to adjust.
+ /// The negative sign string based on the current culture's number format.
+ /// Indicates if the number is negative, determining if a negative sign should be prefixed.
+ void AdjustDisplayText(ref string displayText, string? negativeSign, bool isNegative)
+ {
+ if (negativeSign == null)
+ {
+ return;
+ }
+ string prefix = isNegative ? negativeSign : string.Empty;
+ displayText = prefix + displayText.Replace(negativeSign, string.Empty, StringComparison.CurrentCulture).TrimStart('0');
+ PrefixZeroIfNeeded(ref displayText, isNegative, GetNumberFormat());
+ }
+
+ ///
+ /// Ensures the display text is valid and adjusts the caret position accordingly.
+ ///
+ /// The current display text to be validated and adjusted.
+ /// The current position of the caret within the text.
+ /// Indicates if the operation is triggered by a backspace action.
+ /// The current numeric value, used in certain validation contexts.
+ void PrefixZeroAndUpdateCursor(ref string displayText, ref int caretPosition, bool isBackspace = false, double value = 0)
+ {
+ string negativeSign = GetNegativeSign(GetNumberFormat());
+ string decimalSeparator = GetNumberDecimalSeparator(GetNumberFormat());
+
+ if (AllowNull && string.IsNullOrEmpty(displayText.TrimStart(negativeSign[0]).TrimEnd(decimalSeparator[0])))
+ {
+ // Sets display text as empty when it has no value.
+ displayText = string.Empty;
+ caretPosition = 0;
+ }
+ else
+ {
+ bool isNegative = IsNegative(displayText, GetNumberFormat());
+ bool isZeroNeeded = PrefixZeroIfNeeded(ref displayText, isNegative, GetNumberFormat());
+
+ if (isBackspace && _textBox != null)
+ {
+ // The negative sign is removed only after the value is 0.
+ // For example, -1 will be changed into -0 followed by 0.
+ displayText = (isNegative && value == 0 && caretPosition == 2) ?
+ displayText.TrimStart(negativeSign[0]) : displayText;
+ isNegative = IsNegative(displayText, GetNumberFormat());
+ caretPosition = string.IsNullOrEmpty(_textBox.Text.Substring(_textBox.CursorPosition, _textBox.SelectionLength)) ?
+ (caretPosition - 1) : caretPosition;
+ }
+
+ if (isZeroNeeded)
+ {
+ // It sets the cursor after 0 if the cursor position is before 0. We should not allow cursor before 0.
+ caretPosition = isNegative ? ((caretPosition > 1) ? caretPosition : 2) :
+ ((caretPosition > 0) ? caretPosition : 1);
+ }
+ }
+ }
+
+ ///
+ /// Converts the string into a nullable double.
+ ///
+ double? Parse(string input)
+ {
+ double? value = null;
+ if (string.IsNullOrEmpty(input))
+ {
+ value = AllowNull ? value : 0.0;
+ }
+ else
+ {
+ if (Culture != null && Culture.TextInfo.IsRightToLeft)
+ {
+ if (double.TryParse(input, NumberStyles.Number, GetNumberFormat(), out double parsedValue))
+ {
+ value = parsedValue;
+ }
+ }
+ else
+ {
+ if (double.TryParse(input, GetNumberFormat(), out double parsedValue))
+ {
+ value = parsedValue;
+ }
+ }
+ }
+
+ return value;
+ }
+
+ ///
+ /// Checks if number input is within allowed min and max range and adjusts accordingly.
+ ///
+ double? ValidateMinMax(double? value)
+ {
+ value ??= AllowNull ? value : 0;
+ if (value != null)
+ {
+ return Math.Clamp((double)value, Minimum, Maximum);
+ }
+
+ return value;
+ }
+
+ ///
+ /// Updates display text on the control, resetting to default when necessary.
+ ///
+ internal void UpdateDisplayText(double? value, bool resetCursor = true)
+ {
+ if (_textBox != null)
+ {
+ if (value == null || double.IsNaN((double)value))
+ {
+ double? defaultValue = ValidateMinMax(0.0);
+ _textBox.Text = AllowNull ? string.Empty :
+ defaultValue?.ToString(GetNumberFormat());
+ }
+ else
+ {
+ // Added to resolve the decimal seperator removal issue in OnKeyFocus.
+ if (_textBox.Text != null)
+ {
+ if (ValueChangeMode == ValueChangeMode.OnKeyFocus)
+ {
+ _ = double.TryParse(_textBox.Text, out double result);
+ if (!_textBox.Text.EndsWith(GetNumberDecimalSeparator(GetNumberFormat())) && result != value)
+ {
+ _textBox.Text = value?.ToString(GetNumberFormat());
+ }
+ }
+ else
+ {
+ _textBox.Text = value?.ToString(GetNumberFormat());
+ }
+ }
+ else
+ {
+ _textBox.Text = value?.ToString(GetNumberFormat());
+ }
+ }
+
+ if (resetCursor && _textBox.Text != null)
+ {
+ _textBox.CursorPosition = _textBox.Text.Length;
+ }
+ }
+ }
+
+ ///
+ /// Updates the placeholder text by the given value.
+ ///
+ void UpdatePlaceHolderText(string value)
+ {
+ if (_textBox != null)
+ {
+ _textBox.Placeholder = value;
+ }
+ }
+
+ ///
+ /// Updates the text box editability by the given value.
+ ///
+ void UpdateTextBoxEditability(bool value)
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+ _textBox.IsReadOnly = !value;
+#if ANDROID
+ if (!_textBox.IsReadOnly)
+ {
+ if (_textBox.Handler != null && _textBox.Handler?.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry)
+ {
+ androidEntry.KeyListener = global::Android.Text.Method.DigitsKeyListener.GetInstance(KEYS);
+ }
+ }
+#endif
+ }
+
+ ///
+ /// Helps to wire the events.
+ ///
+ void HookEvents()
+ {
+ UnHookEvents();
+ Loaded += OnLoaded;
+ if (_textBox != null)
+ {
+ HookTextBoxEvents();
+ SetTextBoxProperties();
+ UpdateClearButtonVisibility();
+ UpdateEntryVisibility();
+ UpdateMaximumFractionDigit();
+ }
+
+ UpdateVisualState();
+
+ }
+
+ ///
+ /// Initializes the internal TextBox control with essential properties for the NumericEntry.
+ ///
+ void SetTextBoxProperties()
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+ _textBox.Placeholder = Placeholder;
+ _textBox.IsReadOnly = !IsEditable;
+ _textBox.Drawable = this;
+#if ANDROID
+ _textBox.Keyboard = Keyboard.Numeric;
+#endif
+ }
+
+ ///
+ /// Attaches event handlers for the required events on the text box.
+ ///
+ void HookTextBoxEvents()
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+#if !ANDROID
+ _textBox.TextChanged += OnTextBoxTextChanged;
+#endif
+ _textBox.Completed += TextBox_Completed;
+ _textBox.HandlerChanged += TextBox_HandlerChanged;
+ _textBox.Focused += TextBoxOnGotFocus;
+ _textBox.Unfocused += TextBoxOnLostFocus;
+ }
+
+ ///
+ /// Invokes the Completed event when the text input is finished.
+ ///
+ /// The object that raised the event.
+ /// The event arguments.
+ void TextBox_Completed(object? sender, EventArgs eventArgs)
+ {
+ Completed?.Invoke(this, eventArgs);
+ }
+
+#if IOS
+ ///
+ /// Removes a custom keyboard accessory view for iOS devices, specifically for iPhone.
+ ///
+ void RemoveCustomKeyboard()
+ {
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
+ {
+ RemoveToolBarItems();
+ if (_deviceRotatedObserver != null)
+ {
+ NSNotificationCenter.DefaultCenter.RemoveObserver(_deviceRotatedObserver);
+ }
+ _deviceRotatedObserver = null;
+ }
+ }
+
+ ///
+ /// Adds a custom keyboard accessory view for iOS devices, specifically for iPhone.
+ ///
+ void AddCustomKeyboard()
+ {
+ if (_textBox is View view && view.Handler != null && view.Handler.PlatformView is UIKit.UITextField iOSEntry)
+ {
+ if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
+ {
+ AddToolBarItems();
+ _deviceRotatedObserver = NSNotificationCenter.DefaultCenter.AddObserver(new NSString("UIDeviceOrientationDidChangeNotification"), DeviceRotated);
+ iOSEntry.InputAccessoryView ??= _toolbarView;
+ }
+ }
+ }
+#endif
+
+ ///
+ /// Configures platform-specific behavior for Windows Entry controls.
+ /// This method is called when the Entry's handler changes.
+ ///
+ /// The Entry control whose handler has changed, typically a SfEntryView instance.
+ void WindowEntryHandler(object? sender)
+ {
+ if (sender is SfEntryView textBox)
+ {
+#if WINDOWS
+ if (textBox.Handler is not null && textBox.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox windowTextBox)
+ {
+ windowTextBox.PreviewKeyDown += OnTextBoxPreviewKeyDown;
+ windowTextBox.Paste += OnTextBoxPaste;
+ windowTextBox.CuttingToClipboard += OnTextBoxCuttingToClipboard;
+ windowTextBox.SelectionChanging += OnTextBoxSelectionChanging;
+ windowTextBox.PointerEntered += OnPointerEntered;
+ windowTextBox.PointerExited += OnPointerExited;
+ }
+#endif
+ }
+ }
+
+ ///
+ /// Configures platform-specific behavior for Android Entry controls.
+ /// This method is called when the Entry's handler changes.
+ ///
+ /// The Entry control whose handler has changed, typically a SfEntryView instance.
+ void AndroidEntryHandler(object? sender)
+ {
+#if ANDROID
+ if ((sender is SfEntryView textBox) && textBox.Handler != null && textBox.Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry)
+ {
+ androidEntry.EditorAction += AndroidEntry_EditorAction;
+ androidEntry.TextChanged += OnTextBoxTextChanged;
+ androidEntry.BeforeTextChanged += AndroidEntry_BeforeTextChanged;
+ androidEntry.AfterTextChanged += AndroidEntry_AfterTextChanged;
+ androidEntry.KeyListener = global::Android.Text.Method.DigitsKeyListener.GetInstance(KEYS);
+ androidEntry.Click += AndroidEntry_Click;
+ // Disable EmojiCompat to prevent the IllegalArgumentException crash on Android when start typing text.
+ androidEntry.EmojiCompatEnabled = false;
+ }
+#endif
+ }
+
+ ///
+ /// Configures platform-specific behavior for iOS and MacCatalyst Entry controls.
+ /// This method is called when the Entry's handler changes.
+ ///
+ /// The Entry control whose handler has changed, typically a SfEntryView instance.
+ void IOSEntryHandler(object? sender)
+ {
+
+#if MACCATALYST || IOS
+ if ((sender is not SfEntryView textBox) || textBox == null)
+ {
+ return;
+ }
+
+ if (textBox.Handler != null && textBox.Handler.PlatformView is UIKit.UITextField macEntry)
+ {
+ _uiEntry = macEntry;
+ macEntry.ShouldChangeCharacters += ValidateTextChanged;
+ macEntry.BorderStyle = UITextBorderStyle.None;
+ macEntry.KeyboardType = UIKeyboardType.DecimalPad;
+ }
+#if IOS
+ if (textBox.Handler != null)
+ {
+ AddCustomKeyboard();
+ }
+ else
+ {
+ RemoveCustomKeyboard();
+ }
+#endif
+ if (textBox.Handler == null && _uiEntry != null)
+ {
+ _uiEntry.ShouldChangeCharacters -= ValidateTextChanged;
+ _uiEntry = null;
+ }
+#endif
+ }
+
+ ///
+ /// Handles changes to the TextBox control in a .NET MAUI application.
+ /// This method is triggered whenever the TextBox's state changes,
+ /// allowing for custom behavior in response to those changes.
+ ///
+ /// The source of the event, typically the TextBox.
+ /// Event arguments containing data related to the change event.
+ protected virtual void TextBox_HandlerChanged(object? sender, EventArgs e)
+ {
+ WindowEntryHandler(sender);
+
+ if (_textBox != null)
+ {
+ BackgroundColor ??= Colors.White;
+ if(_textBox.Handler is not null)
+ {
+ SetBinding();
+ }
+ }
+
+ AndroidEntryHandler(sender);
+ IOSEntryHandler(sender);
+ }
+
+#if ANDROID
+
+ ///
+ /// Handles the completion of an editor action on an Android Entry.
+ /// This includes transitioning focus when "Next" or "Done" actions are invoked from the keyboard.
+ ///
+ /// The object that triggered the event, typically an Android Entry component.
+ /// The event arguments containing information about the editor action.
+ void AndroidEntry_EditorAction(object? sender, Android.Widget.TextView.EditorActionEventArgs e)
+ {
+ if (e.ActionId == Android.Views.InputMethods.ImeAction.Next || e.ActionId == Android.Views.InputMethods.ImeAction.Done)
+ {
+ ////Null
+ _textBox?.Unfocus();
+ }
+ }
+
+ ///
+ /// Handles the click event for an Android Entry control.
+ /// Adjusts the cursor position when the text contains a negative sign to prevent accidental deletion.
+ ///
+ /// The object that triggered the event, typically an Android Entry component.
+ /// The event arguments for the click event.
+ void AndroidEntry_Click(object? sender, EventArgs e)
+ {
+ if (sender is AndroidX.AppCompat.Widget.AppCompatEditText entry && entry != null)
+ {
+ if (entry.Text != null)
+ {
+ if (entry.Text.Contains('-', StringComparison.Ordinal) && _textBox != null && _textBox.CursorPosition < 1)
+ {
+ _textBox.CursorPosition = 1;
+ }
+ }
+ }
+ }
+#endif
+
+ ///
+ /// Removes any existing event handlers from the text box.
+ ///
+ void UnHookEvents()
+ {
+ if (_textBox != null)
+ {
+#if !ANDROID
+ _textBox.TextChanged -= OnTextBoxTextChanged;
+#endif
+ _textBox.Completed -= TextBox_Completed;
+ _textBox.HandlerChanged -= TextBox_HandlerChanged;
+ _textBox.Focused -= TextBoxOnGotFocus;
+ _textBox.Unfocused -= TextBoxOnLostFocus;
+
+ }
+ }
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Update the TextInputLayout properties
+ ///
+ internal virtual void UpdateTextInputLayoutUI()
+ {
+ if (_textInputLayout != null)
+ {
+ _textInputLayout.ShowClearButton = ShowClearButton;
+ _textInputLayout.ClearButtonPath = ClearButtonPath;
+ _textInputLayout.ClearButtonColor = ClearButtonColor;
+ }
+ }
+
+ ///
+ /// Checks if a highlight should be cleared based on the visibility and bounds
+ /// of the effects renderer.
+ ///
+ internal void ClearHighlightIfNecessary()
+ {
+ if (!_isClearButtonVisible && _effectsRenderer != null &&
+ _effectsRenderer.HighlightBounds.Width > 0 && _effectsRenderer.HighlightBounds.Height > 0)
+ {
+ _effectsRenderer.RemoveHighlight();
+ }
+ }
+
+ ///
+ /// Gets the minimum size required for the control's components.
+ ///
+ internal virtual void GetMinimumSize()
+ {
+ if (!IsTextInputLayout)
+ {
+#if !ANDROID
+ MinimumHeightRequest = ButtonSize;
+#else
+ MinimumHeightRequest = ButtonSize;
+#endif
+ MinimumWidthRequest = 2 * ButtonSize;
+
+ }
+ }
+
+ ///
+ /// Set RTL flow direction method.
+ ///
+ internal bool IsRTL()
+ {
+ return ((this as IVisualElementController).EffectiveFlowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft;
+ }
+
+ ///
+ /// Check the touch point in the clear button rect.
+ ///
+ internal bool ClearButtonClicked(Point touchPoint) =>
+ _clearButtonRectF.Contains(touchPoint) && _isClearButtonVisible;
+
+ ///
+ /// Updates the bounds of the UI elements based on the specified rectangle.
+ ///
+ /// The rectangle defining the new bounds for the elements.
+ /// A boolean indicating whether to override existing bounds.
+ /// Defaults to false, meaning existing bounds will be preserved unless specified.
+ internal virtual void UpdateElementsBounds(RectF bounds, bool isOverride = false)
+ {
+ _previousRectBounds = bounds;
+ if (!isOverride)
+ {
+ _buttonSpace = 0;
+ if (!IsTextInputLayout)
+ {
+ ConfigureClearButton(bounds);
+ }
+ SetTextBoxMargin();
+ }
+ _numericEntrySemanticsNodes.Clear();
+ InvalidateSemantics();
+ UpdateEffectsRendererBounds();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Updates the bounds of effects (such as shadows or highlights) according to the specified rectangle area.
+ ///
+ /// The bounding rectangle defining the area within which effects should be applied.
+ internal void UpdateEffectsBounds(RectF bounds)
+ {
+ if ((_effectsRenderer is null))
+ {
+ return;
+ }
+ _effectsRenderer.RippleBoundsCollection.Add(bounds);
+ _effectsRenderer.HighlightBoundsCollection.Add(bounds);
+ }
+
+ ///
+ /// Updates the bounds of the effects renderer, ensuring it matches the current layout configuration of the control.
+ ///
+ internal virtual void UpdateEffectsRendererBounds()
+ {
+ if (_effectsRenderer != null)
+ {
+ ClearEffectsBoundsCollection();
+
+ if (_isClearButtonVisible)
+ {
+ UpdateEffectsBounds(_clearButtonRectF);
+ _effectsRenderer.RippleBoundsCollection.Add(_clearButtonRectF);
+ }
+ }
+ }
+
+ ///
+ /// Draws the entry user interface elements within the specified rectangle on the given canvas.
+ ///
+ /// The canvas where the entry UI elements are drawn.
+ /// The rectangle defining the area to be updated and redrawn.
+ internal virtual void DrawEntryUI(ICanvas canvas, Rect dirtyRect)
+ {
+ if (_textBox is null || IsTextInputLayout)
+ {
+ return;
+ }
+
+ InitializeCanvas(canvas);
+ if (_entryRenderer != null)
+ {
+ DrawClearButton(canvas);
+ DrawBorder(canvas, dirtyRect);
+ }
+
+ DrawEffects(canvas);
+ }
+
+ ///
+ /// Adds a semantics node for a given element in the control.
+ ///
+ /// The rectangular bounds of the element.
+ /// The ID of the semantics node.
+ /// The text description for the node, used for accessibility.
+ void AddSemanticsNode(RectF bounds, int id, string description)
+ {
+ SemanticsNode node = new SemanticsNode
+ {
+ Id = id,
+ Bounds = new Rect(bounds.X, bounds.Y, bounds.Width, bounds.Height),
+ Text = $"{description} double tap to activate"
+ };
+ _numericEntrySemanticsNodes.Add(node);
+ }
+
+ ///
+ /// Update the visual states.
+ ///
+ internal virtual void UpdateVisualState()
+ {
+ if (IsEnabled)
+ {
+ VisualStateManager.GoToState(this, "Normal");
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, "Disabled");
+ }
+ }
+
+ ///
+ /// Updates the value property by display text value.
+ ///
+ internal void UpdateValue()
+ {
+ if (_textBox != null)
+ {
+#if (MACCATALYST || IOS)
+ string accentKeys = "`^ˆ˙´˳¨ʼ¯ˍ˝˚ˀ¸ˇ˘˜˛‸";
+ GetValidText(accentKeys);
+ if (_isNotValidText)
+ {
+ return;
+ }
+#endif
+ double? value = Parse(_textBox.Text);
+ value = ValidateMinMax(value);
+ SetValue(ValueProperty, value);
+ UpdateDisplayText(Value);
+ }
+ }
+
+ #endregion
+
+ #region Interface Methods
+
+ ///
+ /// Handles touch events for the control, interpreting the touch interactions
+ /// to trigger specific behaviors or actions such as presses or focus changes.
+ ///
+ /// The event arguments containing data about the touch interaction.
+ public virtual void OnTouch(PointerEventArgs e)
+ {
+ if (IsTextInputLayout)
+ { return; }
+ UpdateTouchPoint(e.TouchPoint);
+
+ switch (e.Action)
+ {
+ case PointerActions.Pressed:
+ HandlePressed(e);
+ break;
+ case PointerActions.Released:
+ case PointerActions.Cancelled:
+ case PointerActions.Exited:
+ HandleReleasedOrCancelled(e);
+ break;
+ }
+ }
+
+ ///
+ /// Method that handles the logic when the clear button is tapped.
+ ///
+ /// The event arguments for the clear button touch up interaction.
+ void OnClearButtonTouchUp(PointerEventArgs e)
+ {
+ UpdateDisplayText(null);
+ if (IsDescriptionNotSetByUser)
+ {
+ SemanticProperties.SetDescription(this, "Clear button pressed");
+ }
+ SemanticScreenReader.Announce(SemanticProperties.GetDescription(this));
+ _numericEntrySemanticsNodes.Clear();
+ InvalidateSemantics();
+ }
+
+ ///
+ /// Invoked during a key down event, allowing the control to handle key down actions.
+ ///
+ /// The containing details of the key event.
+ public void OnKeyDown(KeyEventArgs args)
+ {
+#if MACCATALYST
+ OnTextBoxPreviewKeyDown(args);
+#endif
+ }
+
+ ///
+ /// Invoked during a key up event, providing an opportunity to handle key release actions.
+ ///
+ /// The containing details of the key event.
+ public void OnKeyUp(KeyEventArgs args)
+ {
+
+ }
+
+ ///
+ /// Invoked when the clear icon is pressed within the text input layout, clearing the display text.
+ ///
+ void ITextInputLayout.ClearIconPressed()
+ {
+ UpdateDisplayText(null);
+ }
+
+ ///
+ /// Invoked when the down button is pressed within the text input layout, decreasing the value if possible.
+ ///
+ void ITextInputLayout.DownButtonPressed()
+ {
+ DownButtonPressed();
+ }
+ internal virtual void DownButtonPressed()
+ {
+
+ }
+
+ void ITextInputLayout.UpButtonPressed()
+ {
+ UpButtonPressed();
+ }
+
+ internal virtual void UpButtonPressed()
+ {
+
+ }
+
+ ///
+ /// Determines if this keyboard listener can become the first responder, indicating it is able to receive keyboard input.
+ ///
+ bool IKeyboardListener.CanBecomeFirstResponder
+ {
+ get { return true; }
+ }
+
+ ///
+ /// Event handler invoked on tap events, typically used to bring focus to the control.
+ ///
+ /// The containing details of the tap event.
+ public void OnTap(TapEventArgs e)
+ {
+ Focus();
+ }
+
+ ///
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfNumericEntryStyles();
+ }
+
+ ///
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+
+ }
+
+ ///
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+
+ }
+
+ #endregion
+
+ }
+}
diff --git a/maui/src/NumericEntry/SfNumericEntry.Properties.cs b/maui/src/NumericEntry/SfNumericEntry.Properties.cs
new file mode 100644
index 00000000..fcfbb182
--- /dev/null
+++ b/maui/src/NumericEntry/SfNumericEntry.Properties.cs
@@ -0,0 +1,1838 @@
+using System.Globalization;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Path = Microsoft.Maui.Controls.Shapes.Path;
+#if WINDOWS
+using Windows.Globalization.NumberFormatting;
+#elif IOS
+using UIKit;
+#endif
+
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ /// The class allows users to input numeric values with various formatting options.
+ /// It provides support for features like value range constraints, numeric formats, and culture-specific settings.
+ ///
+ ///
+ /// The below example demonstrates how to initialize the .
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public partial class SfNumericEntry
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.The return command will trigger whenever the return key is pressed. The default value is null.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ReturnCommandProperty =
+ BindableProperty.Create(
+ nameof(ReturnCommand),
+ typeof(ICommand),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.Default,
+ null,
+ OnReturnCommandPropertyChanged);
+
+ ///
+ /// Identifies the bindable property. ReturnCommandParameter is a type of object and it specifies the parameter for the ReturnCommand.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ReturnCommandParameterProperty =
+ BindableProperty.Create(
+ nameof(ReturnCommandParameter),
+ typeof(object),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.Default,
+ null,
+ OnReturnCommandParameterPropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ValueProperty =
+ BindableProperty.Create(
+ nameof(Value),
+ typeof(double?),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.TwoWay,
+ propertyChanged: OnValueChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty MinimumProperty =
+ BindableProperty.Create(
+ nameof(Minimum),
+ typeof(double),
+ typeof(SfNumericEntry),
+ double.MinValue,
+ BindingMode.Default,
+ propertyChanged: OnMinValueChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty MaximumProperty =
+ BindableProperty.Create(
+ nameof(Maximum),
+ typeof(double),
+ typeof(SfNumericEntry),
+ double.MaxValue,
+ BindingMode.Default,
+ propertyChanged: OnMaxValueChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty MaximumNumberDecimalDigitsProperty =
+ BindableProperty.Create(
+ nameof(MaximumNumberDecimalDigits),
+ typeof(int),
+ typeof(SfNumericEntry),
+ 2,
+ BindingMode.Default,
+ propertyChanged: OnMaximumNumberDecimalDigitsChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty CustomFormatProperty =
+ BindableProperty.Create(
+ nameof(CustomFormat),
+ typeof(string),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.Default,
+ propertyChanged: OnCustomFormatChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty PlaceholderProperty =
+ BindableProperty.Create(
+ nameof(Placeholder),
+ typeof(string),
+ typeof(SfNumericEntry),
+ string.Empty,
+ BindingMode.Default,
+ propertyChanged: OnPlaceholderChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty AllowNullProperty =
+ BindableProperty.Create(
+ nameof(AllowNull),
+ typeof(bool),
+ typeof(SfNumericEntry),
+ true,
+ BindingMode.Default,
+ propertyChanged: OnAllowNullValueChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty IsEditableProperty =
+ BindableProperty.Create(
+ nameof(IsEditable),
+ typeof(bool),
+ typeof(SfNumericEntry),
+ true,
+ BindingMode.Default,
+ propertyChanged: OnIsEditableChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty EntryVisibilityProperty =
+ BindableProperty.Create(
+ nameof(EntryVisibility),
+ typeof(Visibility),
+ typeof(SfNumericEntry),
+ Visibility.Visible,
+ BindingMode.Default,
+ propertyChanged: OnEntryVisibilityChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty CultureProperty =
+ BindableProperty.Create(
+ nameof(Culture),
+ typeof(CultureInfo),
+ typeof(SfNumericEntry),
+ CultureInfo.CurrentCulture,
+ BindingMode.Default,
+ propertyChanged: OnCultureChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty ShowClearButtonProperty =
+ BindableProperty.Create(
+ nameof(ShowClearButton),
+ typeof(bool),
+ typeof(SfNumericEntry),
+ true,
+ BindingMode.Default,
+ propertyChanged: OnShowClearButtonChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty PercentDisplayModeProperty =
+ BindableProperty.Create(
+ nameof(PercentDisplayMode),
+ typeof(PercentDisplayMode),
+ typeof(SfNumericEntry),
+ PercentDisplayMode.Compute,
+ BindingMode.Default,
+ propertyChanged: OnPercentDisplayModePropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly BindableProperty ShowBorderProperty =
+ BindableProperty.Create(
+ nameof(ShowBorder),
+ typeof(bool),
+ typeof(SfNumericEntry),
+ true,
+ BindingMode.Default,
+ propertyChanged: OnShowBorderPropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty PlaceholderColorProperty =
+ BindableProperty.Create(
+ nameof(PlaceholderColor),
+ typeof(Color),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.Default,
+ propertyChanged: OnPlaceholderColorPropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ClearButtonColorProperty =
+ BindableProperty.Create(
+ nameof(ClearButtonColor),
+ typeof(Color),
+ typeof(SfNumericEntry),
+ ClearIconStrokeColor,
+ BindingMode.Default,
+ propertyChanged: OnClearButtonColorPropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty StrokeProperty =
+ BindableProperty.Create(
+ nameof(Stroke),
+ typeof(Brush),
+ typeof(SfNumericEntry),
+ GetDefaultStroke(),
+ BindingMode.Default,
+ propertyChanged: OnStrokePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ /// The identifier for bindable property.
+ public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ /// The identifier for bindable property.
+ public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ /// The identifier for bindable property.
+ public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ /// The identifier for bindable property.
+ public static readonly BindableProperty FontAutoScalingEnabledProperty = FontElement.FontAutoScalingEnabledProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ /// The identifier for bindable property.
+ public static readonly BindableProperty TextColorProperty =
+ BindableProperty.Create(
+ nameof(TextColor),
+ typeof(Color),
+ typeof(SfNumericEntry),
+ Colors.Black,
+ BindingMode.Default,
+ null,
+ OnITextElementPropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty CursorPositionProperty =
+ BindableProperty.Create(
+ nameof(CursorPosition),
+ typeof(int),
+ typeof(SfNumericEntry),
+ 0,
+ BindingMode.Default);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty SelectionLengthProperty =
+ BindableProperty.Create(
+ nameof(SelectionLength),
+ typeof(int),
+ typeof(SfNumericEntry),
+ 0,
+ BindingMode.Default);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ValueChangeModeProperty =
+ BindableProperty.Create(
+ nameof(ValueChangeMode),
+ typeof(ValueChangeMode),
+ typeof(SfNumericEntry),
+ ValueChangeMode.OnLostFocus,
+ BindingMode.Default,
+ propertyChanged: OnValueChangeModePropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty HorizontalTextAlignmentProperty =
+ BindableProperty.Create(
+ nameof(HorizontalTextAlignment),
+ typeof(TextAlignment),
+ typeof(SfNumericEntry),
+ TextAlignment.Start,
+ BindingMode.Default,
+ null,
+ OnHorizontalTextAlignmentPropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty VerticalTextAlignmentProperty =
+ BindableProperty.Create(
+ nameof(VerticalTextAlignment),
+ typeof(TextAlignment),
+ typeof(SfNumericEntry),
+ TextAlignment.Center,
+ BindingMode.Default,
+ null,
+ OnVerticalTextAlignmentPropertyChanged);
+
+ ///
+ /// Identifies the bindable property. This property can be used to change the ReturnType.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ReturnTypeProperty =
+ BindableProperty.Create(
+ nameof(ReturnType),
+ typeof(ReturnType),
+ typeof(SfNumericEntry),
+ ReturnType.Default,
+ BindingMode.Default,
+ null,
+ OnReturnTypePropertyChanged);
+
+ ///
+ /// Identifies the bindable property. This property can be used to Customize the clear button of NumericEntry control.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty ClearButtonPathProperty =
+ BindableProperty.Create(
+ nameof(ClearButtonPath),
+ typeof(Path),
+ typeof(SfNumericEntry),
+ null,
+ BindingMode.OneWay,
+ null,
+ OnClearButtonPathChanged);
+
+#if WINDOWS
+ ///
+ /// Identifies dependency property.
+ ///
+ static readonly BindableProperty NumberFormatterProperty =
+ BindableProperty.Create(nameof(NumberFormatter), typeof(INumberFormatter), typeof(SfNumericEntry), null, propertyChanged: OnNumberFormatChanged);
+#endif
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a color that describes the stroke.
+ ///
+ ///
+ /// The default value is Colors.LightGray
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public Brush Stroke
+ {
+ get { return (Brush)GetValue(StrokeProperty); }
+ set { SetValue(StrokeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a color that describes the color of placeholder text.
+ ///
+ ///
+ /// The default value is Colors.Black.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public Color PlaceholderColor
+ {
+ get { return (Color)GetValue(PlaceholderColorProperty); }
+ set { SetValue(PlaceholderColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a color that describes the color of clear button.
+ ///
+ ///
+ /// The default value is Colors.Black.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public Color ClearButtonColor
+ {
+ get { return (Color)GetValue(ClearButtonColorProperty); }
+ set { SetValue(ClearButtonColorProperty, value); }
+ }
+
+
+ ///
+ /// Gets or sets the value that indicates whether the font for the NumericEntry text is bold,italic, or neither.
+ ///
+ /// Specifies the font attributes.The default value is
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font family for the text of .
+ ///
+ /// Specifies the font family.The default value is string.empty.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of FontSize. This property can be used to the size of the font for .
+ ///
+ /// Specifies the font size.The default value is 14d.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ [System.ComponentModel.TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ ///
+ /// Enables automatic font size adjustment based on device settings.
+ ///
+ /// The default value is false.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool FontAutoScalingEnabled
+ {
+ get { return (bool)GetValue(FontAutoScalingEnabledProperty); }
+ set { SetValue(FontAutoScalingEnabledProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font for the text of .
+ ///
+ public Microsoft.Maui.Font Font => (Microsoft.Maui.Font)GetValue(FontElement.FontProperty);
+
+ ///
+ /// Gets or sets the color for the text of the control.
+ ///
+ ///
+ /// It accepts values and the default value is Colors.Black.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the cursor position of the control.
+ ///
+ ///
+ /// The default value is 0.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public int CursorPosition
+ {
+ get { return (int)GetValue(CursorPositionProperty); }
+ set { SetValue(CursorPositionProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection length of the control.
+ ///
+ /// The default value is 0.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public int SelectionLength
+ {
+ get { return (int)GetValue(SelectionLengthProperty); }
+ set { SetValue(SelectionLengthProperty, value); }
+ }
+
+ ///
+ /// Gets or sets whether the value should be updated or not based on this property while entering the value.
+ ///
+ /// The default value is
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public ValueChangeMode ValueChangeMode
+ {
+ get { return (ValueChangeMode)GetValue(ValueChangeModeProperty); }
+ set { SetValue(ValueChangeModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the path to customize the appearance of the clear button in the NumericEntry control.
+ ///
+ /// The default value is null.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ /// The default value is null
+ public Path ClearButtonPath
+ {
+ get { return (Path)GetValue(ClearButtonPathProperty); }
+ set { SetValue(ClearButtonPathProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the ReturnCommand to run when the user presses the return key, either physically or on the on-screen keyboard.
+ ///
+ /// Specifies the ReturnCommand. The default value is null.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ /// Console.WriteLine("Return pressed"));
+ /// this.Content = numericEntry;
+ /// ]]>
+ ///
+ /// ***
+ ///
+ public ICommand ReturnCommand
+ {
+ get { return (ICommand)GetValue(ReturnCommandProperty); }
+ set { SetValue(ReturnCommandProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the parameter object for the that can be used to provide extra information.
+ ///
+ /// The default value is null
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ /// Console.WriteLine($"Return pressed with: {param}"));
+ /// numericEntry.ReturnCommandParameter = "Numeric Entry Data";
+ /// this.Content = numericEntry;
+ /// ]]>
+ ///
+ /// ***
+ ///
+ public object ReturnCommandParameter
+ {
+ get { return (object)GetValue(ReturnCommandParameterProperty); }
+ set { SetValue(ReturnCommandParameterProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the numeric value of a .
+ ///
+ ///
+ /// The default value is null.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public double? Value
+ {
+ get { return (double?)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the numerical minimum for .
+ ///
+ /// The default value is
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public double Minimum
+ {
+ get { return (double)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show or hide the border in SfNumericEntry.
+ ///
+ /// The default value is true
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool ShowBorder
+ {
+ get { return (bool)GetValue(ShowBorderProperty); }
+ set { SetValue(ShowBorderProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the percent display mode that parses the entered text as percentage in specified mode.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ /// This property does not work when a CustomFormat is applied to the control.
+ /// In such cases, the custom format takes precedence, and the percentage display mode is ignored.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public PercentDisplayMode PercentDisplayMode
+ {
+ get { return (PercentDisplayMode)GetValue(PercentDisplayModeProperty); }
+ set { SetValue(PercentDisplayModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the culture for .
+ ///
+ ///
+ /// The default value is "en-US".
+ ///
+ /// ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public CultureInfo Culture
+ {
+ get { return (CultureInfo)GetValue(CultureProperty); }
+ set { SetValue(CultureProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the numerical maximum for .
+ ///
+ /// The default value is .
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public double Maximum
+ {
+ get { return (double)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to change the horizontal alignment of text within the NumericEntry control.
+ ///
+ /// Specifies the text alignment.The default value is .
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
+ set { SetValue(HorizontalTextAlignmentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to change the vertical alignment of text within the NumericEntry control.
+ ///
+ /// Specifies the text alignment.The default value is .
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public TextAlignment VerticalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(VerticalTextAlignmentProperty); }
+ set { SetValue(VerticalTextAlignmentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of ReturnType. This property can be used to set the ReturnType.
+ ///
+ /// The default value is Default
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public ReturnType ReturnType
+ {
+ get { return (ReturnType)GetValue(ReturnTypeProperty); }
+ set { SetValue(ReturnTypeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the maximum number of decimal digits allowed in the numeric input.
+ ///
+ /// The default value is 2.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public int MaximumNumberDecimalDigits
+ {
+ get { return (int)GetValue(MaximumNumberDecimalDigitsProperty); }
+ set { SetValue(MaximumNumberDecimalDigitsProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the format used to specify the formatting of .
+ ///
+ ///
+ /// The default value is null.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// To display a minimum of 2 decimal digits and maximum of 4 digits, with grouping:
+ ///
+ /// To remove group separator:
+ ///
+ /// To display currency format:
+ ///
+ /// To display percentage format, with a minimum of 2 decimal digits:
+ ///
+ ///
+ public string CustomFormat
+ {
+ get { return (string)GetValue(CustomFormatProperty); }
+ set { SetValue(CustomFormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text that is displayed in the control until the value is changed by a user action or some other operation.
+ ///
+ /// The default value is String.Empty
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public string Placeholder
+ {
+ get { return (string)GetValue(PlaceholderProperty); }
+ set { SetValue(PlaceholderProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the allows null value or not.
+ ///
+ ///
+ /// true when allows null value input, otherwise false. The default value is true.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool AllowNull
+ {
+ get { return (bool)GetValue(AllowNullProperty); }
+ set { SetValue(AllowNullProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the allows editing the value or not.
+ ///
+ ///
+ /// true when is editable, otherwise false. The default value is true.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool IsEditable
+ {
+ get { return (bool)GetValue(IsEditableProperty); }
+ set { SetValue(IsEditableProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show or hide the clear button in SfNumericEntry.
+ ///
+ ///
+ /// The default value is true.
+ ///
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool ShowClearButton
+ {
+ get { return (bool)GetValue(ShowClearButtonProperty); }
+ set { SetValue(ShowClearButtonProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show or hide the text box.
+ ///
+ ///
+ /// The default value is .
+ ///
+ internal Visibility EntryVisibility
+ {
+ get { return (Visibility)GetValue(EntryVisibilityProperty); }
+ set { SetValue(EntryVisibilityProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the parent is text input layout.
+ ///
+ internal bool IsTextInputLayout
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a value indicating whether the SemanticProperties.Description is not set by user.
+ ///
+ internal bool IsDescriptionNotSetByUser
+ {
+ get { return _isDescriptionNotSetByUser; }
+ set { _isDescriptionNotSetByUser = value; }
+ }
+
+#if WINDOWS
+
+ ///
+ /// Gets or sets the number formatter used to format the numeric value.
+ ///
+ ///
+ /// An instance of INumberFormatter that defines how numbers should be formatted.
+ ///
+ private INumberFormatter NumberFormatter
+ {
+ get { return (INumberFormatter)GetValue(NumberFormatterProperty); }
+ set { SetValue(NumberFormatterProperty, value); }
+ }
+#endif
+
+ #endregion
+
+ #region Property Changed
+
+ ///
+ /// Called when the font is changed.
+ ///
+ /// The old font value.
+ /// The new font value.
+ public void OnFontChanged(Microsoft.Maui.Font oldValue, Microsoft.Maui.Font newValue)
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Returns the default font size.
+ ///
+ /// Default font size value.
+ double ITextElement.FontSizeDefaultValueCreator()
+ {
+ return 14d;
+ }
+
+ ///
+ /// Invoked when the changes.
+ ///
+ /// The old value of the font attributes.
+ /// The new value of the font attributes.
+ void ITextElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue)
+ {
+ UpdateEntryProperties();
+ }
+
+ ///
+ /// Invoked when the changes.
+ ///
+ /// The old font family value.
+ /// The new font family value.
+ void ITextElement.OnFontFamilyChanged(string oldValue, string newValue)
+ {
+ UpdateEntryProperties();
+ }
+
+ ///
+ /// Invoked when the changes.
+ ///
+ /// The old font size value.
+ /// The new font size value.
+ void ITextElement.OnFontSizeChanged(double oldValue, double newValue)
+ {
+ UpdateEntryProperties();
+ }
+
+ ///
+ /// Invoked when the changes.
+ ///
+ /// The old auto-scaling enabled value.
+ /// The new auto-scaling enabled value.
+ void ITextElement.OnFontAutoScalingEnabledChanged(bool oldValue, bool newValue)
+ {
+ UpdateEntryProperties();
+ }
+
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ static void OnStrokePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnPlaceholderColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry && numericEntry._textBox != null)
+ {
+ numericEntry._textBox.PlaceholderColor = (Color)newValue;
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnClearButtonColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.InvalidateDrawable();
+ numericEntry.UpdateTextInputLayoutUI();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ static void OnITextElementPropertyChanged(BindableObject bindable, object? oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry?.UpdateEntryProperties();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ static void OnValueChangeModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ static void OnPercentDisplayModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.FormatValue();
+ }
+ }
+ ///
+ /// Invoked whenever the is set.
+ ///
+ static void OnShowBorderPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ ///
+ ///
+ ///
+ static void OnClearButtonPathChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ if (numericEntry._textInputLayout != null)
+ {
+ numericEntry.UpdateTextInputLayoutUI();
+ }
+ else
+ {
+ numericEntry.InvalidateDrawable();
+ }
+ }
+ }
+
+ ///
+ /// Occurs when minimum property is changed.
+ ///
+ static void OnMinValueChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+
+ if (numericEntry.Maximum < numericEntry.Minimum)
+ {
+ numericEntry.Maximum = numericEntry.Minimum;
+ }
+
+ if (numericEntry.Value < numericEntry.Minimum)
+ {
+ numericEntry.SetValue(ValueProperty, numericEntry.Minimum);
+ numericEntry.FormatValue();
+ }
+ numericEntry.UpdateButtonColor(numericEntry.Value);
+
+ }
+
+ ///
+ /// Occurs when maximum property is changed.
+ ///
+ static void OnMaxValueChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+
+ if (numericEntry.Minimum > numericEntry.Maximum)
+ {
+ numericEntry.Minimum = numericEntry.Maximum;
+ }
+
+ if (numericEntry.Value > numericEntry.Maximum)
+ {
+ numericEntry.SetValue(ValueProperty, numericEntry.Maximum);
+ numericEntry.FormatValue();
+ }
+
+ numericEntry.UpdateButtonColor(numericEntry.Value);
+ }
+
+ ///
+ /// Occurs when custom format property is changed.
+ ///
+ static void OnCustomFormatChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+
+ numericEntry.FormatValue();
+ numericEntry.UpdateMaximumFractionDigit();
+ }
+
+#if WINDOWS
+ ///
+ /// Occurs when number formatter property is changed.
+ ///
+ static void OnNumberFormatChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.FormatValue();
+ numericEntry.UpdateMaximumFractionDigit();
+ }
+#endif
+
+ static void OnPlaceholderChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.UpdatePlaceHolderText((string)newValue);
+ }
+
+ ///
+ /// Occurs when is editable property is changed.
+ ///
+ static void OnIsEditableChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.UpdateTextBoxEditability((bool)newValue);
+ }
+
+ ///
+ /// Occurs when allow null property is changed.
+ ///
+ static void OnAllowNullValueChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+
+ if (!numericEntry.AllowNull && (numericEntry.Value == null || double.IsNaN((double)numericEntry.Value)))
+ {
+ numericEntry.SetValue(ValueProperty, numericEntry.ValidateMinMax(0.0));
+ numericEntry.FormatValue();
+ }
+ }
+
+ ///
+ /// Occurs when property is changed.
+ ///
+ static void OnEntryVisibilityChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.UpdateEntryVisibility();
+ }
+
+ ///
+ /// Occurs when property is changed.
+ ///
+ static void OnCultureChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.FormatValue();
+ }
+
+ ///
+ /// Property changed method for HorizontalTextAlignment property.
+ ///
+ /// The original source of property changed event.
+ /// The old value of HorizontalTextAlignment property.
+ /// The new value of HorizontalTextAlignment property.
+ static void OnHorizontalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry?.UpdateEntryProperties();
+ }
+ }
+
+ ///
+ /// Property changed method for VerticalTextAlignment property.
+ ///
+ /// The original source of property changed event.
+ /// The old value of VerticalTextAlignment property.
+ /// The new value of VerticalTextAlignment property.
+ static void OnVerticalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry?.UpdateEntryProperties();
+ }
+ }
+
+ ///
+ /// Occurs when MaximumNumberDecimalDigits property is changed.
+ ///
+ ///
+ ///
+ ///
+
+ static void OnMaximumNumberDecimalDigitsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ numericEntry.UpdateMaximumFractionDigit();
+ numericEntry.FormatValue();
+ }
+
+ ///
+ /// Occurs when ReturnType property is changed.
+ ///
+ ///
+ ///
+ ///
+ static void OnReturnTypePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ if (numericEntry != null && numericEntry._textBox != null)
+ {
+ numericEntry._textBox.ReturnType = numericEntry.ReturnType;
+#if IOS
+ numericEntry.UpdateReturnButtonText();
+#endif
+ }
+ }
+ }
+
+ ///
+ /// Property changed method for ReturnCommand property.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnReturnCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry && numericEntry._textBox != null)
+ {
+ numericEntry._textBox.ReturnCommand = numericEntry.ReturnCommand;
+ }
+ }
+
+ ///
+ /// Property changed method for ReturnCommandParameter property.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnReturnCommandParameterPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry && numericEntry._textBox != null)
+ {
+ numericEntry._textBox.ReturnCommandParameter = numericEntry.ReturnCommandParameter;
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ static void OnShowClearButtonChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericEntry numericEntry)
+ {
+ numericEntry.UpdateClearButtonVisibility();
+ numericEntry.UpdateTextInputLayoutUI();
+ }
+
+ }
+
+ ///
+ /// Validates whether the entered text is within allowed constraints.
+ ///
+ /// The object initiating the event.
+ /// Event arguments for validation.
+#if !ANDROID
+ void OnTextBoxTextChanged(object? sender, Microsoft.Maui.Controls.TextChangedEventArgs e)
+#elif ANDROID
+ void OnTextBoxTextChanged(object? sender, Android.Text.TextChangedEventArgs e)
+#endif
+ {
+ UpdateClearButtonVisibility();
+#if ANDROID
+ ValidateTextChanged(sender, e);
+#endif
+ if (ValueChangeMode == ValueChangeMode.OnKeyFocus && _textBox != null && _textBox.IsFocused)
+ {
+#if MACCATALYST || IOS
+ string accentKeys = "`^ˆ˙´˳¨ʼ¯ˍ˝˚ˀ¸ˇ˘˜˛‸";
+ GetValidText(accentKeys);
+ if (_isNotValidText)
+ {
+ return;
+ }
+ else if (string.IsNullOrEmpty(_textBox.Text))
+ {
+ Value = Parse(_textBox.Text);
+ }
+ else if (double.TryParse(_textBox.Text, Culture, out _))
+ {
+ Value = Parse(_textBox.Text);
+ }
+#endif
+
+#if WINDOWS
+ if (_textBox.Text != "-")
+ {
+ Value = Parse(_textBox.Text);
+ }
+#elif ANDROID
+ if (_textBox.Text != "-" && _textBox.Text != GetNumberDecimalSeparator(GetNumberFormat()))
+ {
+ if (_textBox.Text == "-" + GetNumberDecimalSeparator(GetNumberFormat()))
+ {
+ Value = AllowNull ? null : 0.0;
+ return;
+ }
+
+ Value = Parse(_textBox.Text);
+ _textBox.CursorPosition = _cursorPosition;
+ }
+#endif
+ }
+ }
+
+ ///
+ /// Invoked whenever the is changed.
+ ///
+ /// The object that owns the bindable property.
+ /// The old value of the property.
+ /// The new value of the property.
+ static void OnValueChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfNumericEntry numericEntry = (SfNumericEntry)bindable;
+ if (numericEntry._valueUpdating)
+ {
+ return;
+ }
+ numericEntry._valueUpdating = true;
+ double? newValueAsDouble = ConvertToDouble(newValue, oldValue);
+
+ double? old_Value = (double?)oldValue;
+
+ if (newValueAsDouble.HasValue && !double.IsNaN(newValueAsDouble.Value))
+ {
+ newValueAsDouble = ValidateAndUpdateValue(numericEntry, newValueAsDouble.Value);
+ }
+
+ ValidateAndUpdateValue(numericEntry, newValueAsDouble);
+
+ // Do not raise event when old and new value are equal.
+ // After min and max validation, new and old value might be equal.
+ if (old_Value != newValueAsDouble)
+ {
+ if (numericEntry.ValueChanged != null)
+ {
+ RaiseValueChangedEvent(numericEntry, old_Value, newValueAsDouble);
+ }
+ }
+
+ UpdateTextBoxAndButtonColors(numericEntry, newValueAsDouble);
+ }
+
+ #endregion
+ }
+}
diff --git a/maui/src/NumericEntry/SfNumericEntry.cs b/maui/src/NumericEntry/SfNumericEntry.cs
new file mode 100644
index 00000000..859da828
--- /dev/null
+++ b/maui/src/NumericEntry/SfNumericEntry.cs
@@ -0,0 +1,934 @@
+using Syncfusion.Maui.Toolkit.Internals;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+using Syncfusion.Maui.Toolkit.EffectsView;
+using Syncfusion.Maui.Toolkit.EntryRenderer;
+using System.Runtime.CompilerServices;
+using Syncfusion.Maui.Toolkit.TextInputLayout;
+using Syncfusion.Maui.Toolkit.EntryView;
+#if ANDROID
+using Android.Text;
+#elif MACCATALYST || IOS
+using UIKit;
+using Foundation;
+using Microsoft.Maui.ApplicationModel;
+#if IOS
+using CoreGraphics;
+#endif
+#endif
+
+namespace Syncfusion.Maui.Toolkit.NumericEntry
+{
+ ///
+ /// Represents a control that can be used to display and edit numbers.
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ public partial class SfNumericEntry : SfView, ITextElement, ITouchListener, IKeyboardListener, IParentThemeElement, ITextInputLayout
+ {
+ #region Fields
+
+ ///
+ /// Renderer for the dropdown component.
+ ///
+ internal IUpDownButtonRenderer? _entryRenderer;
+
+ ///
+ /// Rectangle for the clear button's area.
+ ///
+ internal RectF _clearButtonRectF;
+
+ ///
+ /// Renderer for effects applied to the control.
+ ///
+ EffectsRenderer? _effectsRenderer;
+
+ ///
+ /// Size of the buttons in the control.
+ ///
+ internal const int ButtonSize = 30;
+
+ ///
+ /// Space between buttons.
+ ///
+ int _buttonSpace;
+
+ ///
+ /// Stroke color for the clear icon.
+ ///
+ static readonly Color ClearIconStrokeColor = Colors.Black;
+
+ ///
+ /// Text input layout associated with the control.
+ ///
+ internal SfTextInputLayout? _textInputLayout;
+
+ ///
+ /// Previous bounds of the rectangle for layout calculations.
+ ///
+ internal RectF _previousRectBounds;
+
+ ///
+ /// Indicates if the clear button is visible.
+ ///
+ internal bool _isClearButtonVisible;
+
+ ///
+ /// Point representing the touch location.
+ ///
+ Point _touchPoint;
+
+ ///
+ /// Current state of the value in the control.
+ ///
+ internal ValueStates _valueStates;
+
+ ///
+ /// Size for the semantics of the clear button.
+ ///
+ internal Size _clearButtonSemanticsSize = Size.Zero;
+
+ ///
+ /// Indicates if the description has been set by the user.
+ ///
+ bool _isDescriptionNotSetByUser;
+
+ ///
+ /// List of semantic nodes for numeric entry.
+ ///
+ readonly List _numericEntrySemanticsNodes = [];
+
+ ///
+ /// Represents the text entry control where user input is displayed.
+ ///
+ internal SfEntryView? _textBox;
+
+ ///
+ /// Gets or sets the maximum positive fraction digits.
+ ///
+ int _maximumPositiveFractionDigit = -1;
+
+ ///
+ /// Gets or sets the maximum negative fraction digits.
+ ///
+ int _maximumNegativeFractionDigit = -1;
+
+ // Flag to avoid duplicate value changing event.
+ // Use this flag only in 'OnValueChanged', do not use anywhere else.
+ bool _valueUpdating = false;
+
+#if MACCATALYST || IOS
+ ///
+ /// Indicates whether the current text in the UI entry is not valid.
+ ///
+ bool _isNotValidText = false;
+
+ ///
+ /// Represents the underlying iOS UI text field used in the control.
+ ///
+ UIKit.UITextField? _uiEntry;
+
+ ///
+ /// Represents the initial corner radius in the control.
+ ///
+ readonly CornerRadius _initialCornderRadius = 6;
+
+#if IOS
+ ///
+ /// Button for returning to the previous screen.
+ ///
+ UIButton? _returnButton;
+
+ ///
+ /// Button for decreasing the numeric value.
+ ///
+ UIButton? _minusButton;
+
+ ///
+ /// Separator button in the UI.
+ ///
+ UIButton? _separatorButton;
+
+ ///
+ /// Views for the lines in the toolbar.
+ ///
+ UIView? _lineView1, _lineView2, _lineView3, _lineView4, _toolbarView;
+
+ ///
+ /// Width of the buttons in the toolbar.
+ ///
+ nfloat _buttonWidth;
+
+ ///
+ /// Observer for device rotation events.
+ ///
+ NSObject? _deviceRotatedObserver;
+
+ ///
+ /// Another separator string for UI layout.
+ ///
+ string? _anotherSeparator;
+#endif
+#elif WINDOWS
+ ///
+ /// Gets or sets the redo text.
+ ///
+ string? _redoText = null;
+
+ ///
+ /// Indicates whether the control has focus.
+ ///
+ internal bool _isFocus = false;
+
+ ///
+ /// It can be used to store a reference to a parent view element.
+ ///
+ Microsoft.Maui.Platform.WrapperView? _grandParentElement;
+
+ ///
+ /// Indicates whether this is the initial assignment or interaction with the control
+ /// (used specifically for first-time operations).
+ ///
+ bool _isFirst = true;
+
+ ///
+ /// Represents the initial corner radius in the control.
+ ///
+ readonly CornerRadius _initialCornderRadius = 5;
+#elif ANDROID
+ ///
+ /// Holds the length of text currently selected by the user.
+ ///
+ int _selectionLength;
+
+ ///
+ /// Represents the current cursor position within the text box.
+ ///
+ int _cursorPosition;
+
+ ///
+ /// Flag indicating whether cursor position adjustments should occur after modifications.
+ ///
+ bool _shiftCursor;
+
+ ///
+ /// Valid characters allowed for direct input within the text field.
+ ///
+ const string KEYS = "1234567890,.-";
+
+ ///
+ /// Indicates whether the text field is being focused for the first time, often used for initializing behavior.
+ ///
+ bool _isFirstFocus = true;
+#endif
+
+#if !WINDOWS
+ ///
+ /// Stores the text prior to the latest input modification.
+ ///
+ string _previousText = string.Empty;
+#endif
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfNumericEntry()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfNumericEntryTheme");
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// To indicate internally created for SfNumericUpDown.
+ public SfNumericEntry(bool isInternallyCreated)
+ {
+ if (isInternallyCreated)
+ {
+ Initialize();
+ }
+ }
+
+ ///
+ /// Destructor of the class.
+ ///
+ ~SfNumericEntry()
+ {
+ UnHookEvents();
+ }
+
+ #endregion
+
+ #region Protected methods
+
+ ///
+ /// Method called when the control's parent changes, allowing for adjustments related to the change in hierarchy or parent container.
+ ///
+ protected override void OnParentChanged()
+ {
+ if (Parent != null)
+ {
+ ValidateParentIsTextInputLayout();
+ }
+ else
+ {
+ IsTextInputLayout = false;
+ _textInputLayout = null;
+ }
+ base.OnParentChanged();
+ }
+
+ ///
+ /// Arranges the content of the control within the specified bounds post-measurement.
+ ///
+ /// The rectangle that defines the bounds available for the content arrangement.
+ /// The actual size used by the arranged content, typically equal to the size of the bounds.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ UpdateElementsBounds(bounds);
+ return base.ArrangeContent(bounds);
+ }
+
+ ///
+ /// Measures the content of the control to determine the desired size based on the given constraints.
+ ///
+ /// The maximum width available for the content.
+ /// The maximum height available for the content.
+ /// The desired size for the content, which should be within the given constraints.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ if (_textBox == null)
+ {
+ return Size.Zero;
+ }
+
+ Size textBoxSize = _textBox.Measure(widthConstraint, heightConstraint);
+ double measuredWidth = double.IsInfinity(widthConstraint) ? textBoxSize.Width : widthConstraint;
+ double measuredHeight = double.IsInfinity(heightConstraint) ? textBoxSize.Height : heightConstraint;
+
+ return new Size(measuredWidth, measuredHeight);
+ }
+
+ ///
+ /// Called during the render pass to draw the control's content onto the canvas.
+ ///
+ /// The canvas to draw on, providing methods and properties for rendering.
+ /// The rectangle area that needs to be redrawn, typically due to invalidation.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ base.OnDraw(canvas, dirtyRect);
+ DrawEntryUI(canvas, dirtyRect);
+
+ #if WINDOWS || MACCATALYST || IOS
+ bool needClip = false;
+
+ #if WINDOWS && NET8_0
+ if (Parent is not Microsoft.Maui.Controls.Frame)
+ {
+ needClip = !IsRTL();
+ }
+ #elif MACCATALYST || IOS
+ needClip = true;
+ #endif
+
+ if (needClip)
+ {
+ Clip = new Microsoft.Maui.Controls.Shapes.RoundRectangleGeometry(_initialCornderRadius, dirtyRect);
+ }
+ #endif
+ }
+
+ ///
+ protected override void OnHandlerChanged()
+ {
+ base.OnHandlerChanged();
+#if ANDROID
+ if (!IsLoaded && _textBox is not null)
+ {
+ _textBox.HeightRequest = HeightRequest;
+ }
+#endif
+ }
+
+ ///
+ /// Called when a property value changes.
+ /// This method is used to react to property updates, typically for updating UI or state.
+ ///
+ ///
+ /// The name of the property that changed. By default, this is automatically provided by the caller.
+ ///
+ protected override void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ if (propertyName == null)
+ {
+ return;
+ }
+
+ switch (propertyName)
+ {
+ case nameof(IsEnabled):
+ if (_textBox != null)
+ {
+ _textBox.IsEnabled = IsEnabled;
+ }
+ break;
+
+ #if WINDOWS
+ case nameof(FlowDirection):
+ SetFlowDirection();
+ break;
+ #endif
+ }
+
+ // Ensure the base method is always called
+ base.OnPropertyChanged(propertyName);
+ }
+
+ ///
+ /// Retrieves the list of semantics nodes for accessibility support,
+ /// which represent how the control is perceived by assistive technologies.
+ ///
+ /// The width constraint for the semantics nodes.
+ /// The height constraint for the semantics nodes.
+ /// A list of semantics nodes that describe the accessible elements within this control.
+ protected override List GetSemanticsNodesCore(double width, double height)
+ {
+ // Ensure any necessary size updates are performed
+ UpdateSemanticsSizes();
+ if (SemanticsDataIsCurrent())
+ {
+ return _numericEntrySemanticsNodes;
+ }
+
+ if (_isClearButtonVisible)
+ {
+ AddSemanticsNode(_clearButtonRectF, 1, "Clear button");
+ }
+
+ return _numericEntrySemanticsNodes;
+ }
+
+ ///
+ /// Called when the numeric entry receives focus.
+ ///
+
+#if ANDROID
+ protected async void OnGotFocus()
+#else
+ protected void OnGotFocus()
+#endif
+ {
+ #if ANDROID
+ // Avoid IndexOutOfBoundException for drag-and-drop text on Android 11 API 30 or below
+ if (Android.OS.Build.VERSION.SdkInt <= Android.OS.BuildVersionCodes.R)
+ {
+ await Task.Delay(150);
+ }
+ #endif
+
+ _textBox?.Focus();
+ VisualStateManager.GoToState(this, "Focused");
+
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ // Set the text in the text box, ensuring non-null entries
+ _textBox.Text = Value != null && !double.IsNaN((double)Value)
+ ? RestrictFractionDigit(Value.Value.ToString(GetNumberFormat()))
+ : AllowNull ? string.Empty : "0";
+
+ // Set cursor position and selection
+ _textBox.CursorPosition = 0;
+ #if WINDOWS
+ if (!_isFirst)
+ {
+ _textBox.CursorPosition = _textBox.Text.Length;
+ _textBox.SelectionLength = 0;
+ }
+ _isFirst = false;
+ #endif
+ #if ANDROID
+ await Task.Delay(1);
+ #endif
+ _textBox.SelectionLength = _textBox.Text.Length;
+
+ // Set accessibility description if not set by the user
+ if (_isDescriptionNotSetByUser)
+ {
+ SemanticProperties.SetDescription(this, $"{Placeholder} NumericEntry");
+ }
+ }
+
+ ///
+ /// Called when the numeric entry loses focus.
+ ///
+ protected void OnLostFocus()
+ {
+#if ANDROID
+ if (IsSamsungDevice() && _textBox != null)
+ {
+ // Ensure _textBox.Text isn't just a decimal separator or a minus sign
+ if (_textBox.Text == GetNumberDecimalSeparator(GetNumberFormat()) || _textBox.Text == "-")
+ {
+ _textBox.Text = AllowNull ? string.Empty : "0";
+ }
+ }
+#endif
+ UpdateValue();
+#if WINDOWS
+ if (_isFocus)
+ {
+ SetInputViewFocus();
+ }
+ else
+ {
+ FormatValue();
+ _isFirst = true;
+ }
+#else
+ FormatValue();
+#endif
+ VisualStateManager.GoToState(this, "Normal");
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the user triggers evaluation of new input by pressing the Enter key, clicking a spin button, or by changing focus.
+ ///
+ public event EventHandler? ValueChanged;
+
+ ///
+ /// Occurs when the user finalizes the text in an numeric entry with the return key.
+ ///
+ public event EventHandler? Completed;
+
+ ///
+ /// Occurs when the control get focused.
+ ///
+ public new event EventHandler? Focused;
+
+ ///
+ /// Occurs when the control get unfocused.
+ ///
+ public new event EventHandler? Unfocused;
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Sets focus to the control, allowing user input.
+ ///
+ public new void Focus()
+ {
+ _textBox?.Focus();
+ }
+
+ ///
+ /// Removes focus from the control, disabling user input.
+ ///
+ public new void Unfocus()
+ {
+ _textBox?.Unfocus();
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// Arguments related to the focus event.
+ internal void RaiseFocusedEvent(FocusEventArgs args)
+ {
+ Focused?.Invoke(this, args);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// Arguments related to the unfocus event.>
+ internal void RaiseUnfocusedEvent(FocusEventArgs args)
+ {
+ Unfocused?.Invoke(this, args);
+ }
+
+ #endregion
+
+ #region Keyboard_iOS
+
+#if IOS
+ ///
+ /// Adds toolbar items to the numeric entry control on iOS.
+ /// This setup includes the return, minus, and separator buttons, as well as the toolbar view. It also handles the setup of any visual lines or dividers within the toolbar.
+ ///
+ void AddToolBarItems()
+ {
+ SetupReturnButton();
+ SetupMinusButton();
+ SetupSeparatorButton();
+ SetupToolbarView();
+ UpdateFrames();
+ }
+
+ ///
+ /// To update the return button text
+ ///
+ void UpdateReturnButtonText()
+ {
+ _returnButton?.SetTitle(GetReturnButtonText(), UIControlState.Normal);
+ }
+
+ ///
+ /// To get the return button text
+ ///
+ string GetReturnButtonText()
+ {
+ string returnText = "return";
+ if (ReturnType != ReturnType.Default)
+ {
+ returnText = ReturnType.ToString().ToLowerInvariant();
+ }
+
+ return SfNumericEntryResources.GetLocalizedString(returnText);
+ }
+
+ ///
+ /// Sets up the return button on the toolbar, configuring its appearance and
+ /// behavior to provide feedback or perform actions when the user completes input.
+ ///
+ void SetupReturnButton()
+ {
+ _returnButton = CreateToolbarButton(GetReturnButtonText());
+ _returnButton.TouchDown += ReturnButton_TouchDown;
+ }
+
+ ///
+ /// Configures the minus button on the toolbar, allowing users to input negative numbers.
+ /// It initializes the button and attaches the necessary event handler.
+ ///
+ void SetupMinusButton()
+ {
+ _minusButton = CreateToolbarButton("-");
+ _minusButton.TouchDown += MinusButton_TouchDown;
+ }
+
+ ///
+ /// Sets up an optional separator button on the toolbar if an additional separator is required.
+ /// This helps in providing quick access to additional input characters or functions.
+ ///
+ void SetupSeparatorButton()
+ {
+ _anotherSeparator = SfNumericEntry.GetAnotherSeparatorText();
+ _separatorButton = CreateToolbarButton(_anotherSeparator);
+ _separatorButton.SetTitle(_anotherSeparator, UIControlState.Normal);
+ _separatorButton.TouchDown += SeparatorButton_TouchDown;
+ }
+
+ ///
+ /// Retrieves an alternative decimal separator text.
+ /// If the current locale's decimal separator is a period ("."), it returns a comma (",").
+ /// Otherwise, it returns a period (".").
+ ///
+ /// A string representing the alternative decimal separator.
+ static string GetAnotherSeparatorText()
+ {
+ string separator = NSLocale.CurrentLocale.DecimalSeparator;
+ if (separator == ".")
+ {
+ return ",";
+ }
+ else
+ {
+ return ".";
+ }
+ }
+
+ ///
+ /// Creates a UIButton for the iOS toolbar with the specified title.
+ ///
+ /// The title to set on the button.
+ /// A UIButton configured with the specified title and styling.
+ static UIButton CreateToolbarButton(string? title)
+ {
+ UIButton button = [];
+ button.SetTitle(title, UIControlState.Normal);
+ button.SetTitleColor(UIColor.Black, UIControlState.Normal);
+ button.BackgroundColor = UIColor.FromRGB(210, 213, 218);
+ return button;
+ }
+
+ ///
+ /// Initializes the toolbar view and adds all configured buttons and lines to it. Sets the background color and ensures the elements are layered correctly for user interaction.
+ ///
+ void SetupToolbarView()
+ {
+ _toolbarView = [];
+ _lineView1 = SetupLineView();
+ _lineView2 = SetupLineView();
+ _lineView3 = SetupLineView();
+ _lineView4 = SetupLineView();
+
+ _toolbarView.AddSubviews(_minusButton, _separatorButton, _returnButton, _lineView1, _lineView2, _lineView3, _lineView4);
+ _toolbarView.BackgroundColor = UIColor.FromRGB(249, 249, 249);
+
+ _toolbarView.BringSubviewToFront(_lineView1);
+ _toolbarView.BringSubviewToFront(_lineView2);
+ _toolbarView.BringSubviewToFront(_lineView3);
+ _toolbarView.BringSubviewToFront(_lineView4);
+ }
+
+ ///
+ /// Creates a line view used for visually dividing toolbar items. Each line is styled with a subtle color to match typical UI conventions on iOS.
+ ///
+ /// A configured UIView representing the line.
+ static UIView SetupLineView()
+ {
+ UIView lineView = new UIView
+ {
+ BackgroundColor = UIColor.FromRGB(175, 175, 180)
+ };
+ return lineView;
+ }
+
+ ///
+ /// Removes handlers associated with iOS toolbar buttons and sets related fields to null.
+ ///
+ void RemoveToolBarItems()
+ {
+ if (_minusButton != null)
+ {
+ _minusButton.TouchDown -= MinusButton_TouchDown;
+ }
+ if (_returnButton != null)
+ {
+ _returnButton.TouchDown -= ReturnButton_TouchDown;
+ }
+ if (_separatorButton != null)
+ {
+ _separatorButton.TouchDown -= SeparatorButton_TouchDown;
+ }
+ _minusButton = _separatorButton = _returnButton = null ;
+ _lineView1 = _lineView2 = _lineView3 = _lineView4 = null;
+ _toolbarView = null;
+ }
+
+ ///
+ /// Handles the touch down event for the separator button on iOS toolbar.
+ ///
+ /// The source of the event.
+ /// The event arguments, usually empty in this context.
+ void SeparatorButton_TouchDown(object? sender, EventArgs e)
+ {
+ if (_textBox != null && _textBox.Text != null)
+ {
+ string decimalSeperator = GetNumberDecimalSeparator(GetNumberFormat());
+ _previousText = _textBox.Text;
+ if (decimalSeperator == _anotherSeparator)
+ {
+ HandleDecimalSeparator(decimalSeperator, CursorPosition);
+ }
+ }
+ }
+
+ ///
+ /// Handles the touch down event for the minus button on iOS toolbar,
+ /// toggling the negative sign of the numeric value.
+ ///
+ /// The source of the event.
+ /// The event arguments, usually empty in this context.
+ void MinusButton_TouchDown(object? sender, EventArgs e)
+ {
+ if (_textBox != null && _textBox.Text != null)
+ {
+ bool isNegative = IsNegative(_textBox.Text, GetNumberFormat());
+ int caretPosition = CursorPosition;
+ _previousText = _textBox.Text;
+ HandleNegativeKey(isNegative, caretPosition);
+ }
+ }
+
+ ///
+ /// Searches for the next focusable element in the UI tree starting from the given element.
+ ///
+ /// The current visual element from which the search starts.
+ /// The next focusable if found; otherwise, null.
+ static VisualElement? FindNextFocusableElement(VisualElement currentElement)
+ {
+ // Start searching from the parent of the current element
+ if (currentElement.Parent is not VisualElement rootElement || rootElement == null)
+ {
+ return null;
+ }
+
+ // Track if we have found the current element in the tree
+ bool foundCurrent = false;
+
+ // Recursive method to search for the next focusable element
+ VisualElement? SearchNextFocusableElement(VisualElement parent)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ foreach (var child in parent.LogicalChildren)
+ {
+ if (foundCurrent && child is VisualElement ve && ve.IsEnabled && ve.IsVisible && ve.Focus())
+ {
+ return ve; // Found the next focusable element
+ }
+
+ if (child == currentElement)
+ {
+ foundCurrent = true; // Mark that we found the current element
+ }
+
+ if (child is VisualElement childVisualElement && childVisualElement.LogicalChildren.Count > 0)
+ {
+ var result = SearchNextFocusableElement(childVisualElement);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ return null;
+ }
+
+ return SearchNextFocusableElement(rootElement);
+ }
+
+ ///
+ /// Handles the touch down event for the return button on iOS toolbar.
+ ///
+ /// The source of the event.
+ /// The event arguments, usually empty in this context.
+ void ReturnButton_TouchDown(object? sender, EventArgs e)
+ {
+ if (_textBox != null)
+ {
+ if (ReturnType == ReturnType.Next)
+ {
+ VisualElement? nextControl = FindNextFocusableElement(this);
+ if(nextControl is not null && nextControl is SfNumericEntry numeric && numeric is not null)
+ {
+ numeric.Focus();
+ }
+ else
+ {
+ nextControl?.Focus();
+ }
+
+ }
+ else
+ {
+ Unfocus();
+ }
+ }
+ }
+
+ ///
+ /// Handler for the device rotation event that updates the toolbar button frames.
+ ///
+ /// The NSNotification triggered by the device rotation.
+ void DeviceRotated(NSNotification notification)
+ {
+ UpdateFrames();
+ }
+
+ ///
+ /// Determines whether the current device orientation is portrait or not.
+ ///
+ /// True if the orientation is portrait or face up/down; otherwise, false.
+ static bool IsPortraitOrientation()
+ {
+ return UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.Portrait || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.PortraitUpsideDown ||
+ UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceUp || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.FaceDown;
+ }
+
+ ///
+ /// Configures the frames for minus, separator, and return buttons based on the screen width and provided adjustments.
+ ///
+ /// The amount to adjust the width of the minus and separator buttons.
+ /// Additional adjustment for the separator button width.
+ void SetButtonFrames(nfloat adjustment, nfloat extraAdjustment)
+ {
+ if (_minusButton != null)
+ {
+ _minusButton.Frame = new CGRect(0, 0, _buttonWidth - adjustment, 50);
+ }
+
+ if (_separatorButton != null)
+ {
+ _separatorButton.Frame = new CGRect(_buttonWidth - adjustment, 0, _buttonWidth + extraAdjustment, 50);
+ }
+
+ if (_returnButton != null && _toolbarView != null)
+ {
+ _returnButton.Frame = new CGRect(_toolbarView.Frame.Size.Width - _buttonWidth + adjustment, 0, _buttonWidth, 50);
+ }
+ }
+
+ ///
+ /// Updates the frames and layout of toolbar buttons, adjusting based on screen orientation and size.
+ /// Ensures the components maintain appropriate spacing and sizing for touch interactions.
+ ///
+ void UpdateFrames()
+ {
+ if (UIDevice.CurrentDevice.Orientation != UIDeviceOrientation.Unknown)
+ {
+ if (_toolbarView == null || _minusButton == null || _separatorButton == null || _returnButton == null
+ || _lineView1 == null || _lineView2 == null || _lineView3 == null || _lineView4 == null)
+ {
+ return;
+ }
+
+ nfloat width = UIScreen.MainScreen.Bounds.Width;
+ _buttonWidth = width / 3;
+ _toolbarView.Frame = new CGRect(0.0f, 0.0f, (float)width, 50.0f);
+ if (IsPortraitOrientation())
+ {
+ if (width <= 375 && width > 320)
+ {
+ SetButtonFrames(2, 4);
+ }
+ else if (width <= 320)
+ {
+ SetButtonFrames((nfloat)1.5, 3);
+
+ }
+ else if (width > 375)
+ {
+ SetButtonFrames(2, 4);
+ }
+ }
+ else if (UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeLeft || UIDevice.CurrentDevice.Orientation == UIDeviceOrientation.LandscapeRight)
+ {
+ if (width <= 667)
+ {
+ SetButtonFrames((nfloat)2.5, 5);
+ }
+ else if (width > 667)
+ {
+ SetButtonFrames((nfloat)3.1, (nfloat)6.1);
+ }
+ }
+ _lineView1.Frame = new CGRect(0, 0, _toolbarView.Frame.Size.Width, 0.5f);
+ _lineView2.Frame = new CGRect(0, _toolbarView.Frame.Size.Height - 0.5f, _toolbarView.Frame.Size.Width, 0.5f);
+ _lineView3.Frame = new CGRect(_minusButton.Frame.Right - 0.5f, 0, 0.5, _minusButton.Frame.Size.Height);
+ _lineView4.Frame = new CGRect(_returnButton.Frame.Left, 0, 0.5, _minusButton.Frame.Size.Height);
+ }
+ }
+#endif
+ #endregion
+ }
+}
diff --git a/maui/src/NumericEntry/SfNumericUpDown.cs b/maui/src/NumericEntry/SfNumericUpDown.cs
new file mode 100644
index 00000000..a1037e0e
--- /dev/null
+++ b/maui/src/NumericEntry/SfNumericUpDown.cs
@@ -0,0 +1,1989 @@
+using PointerEventArgs = Syncfusion.Maui.Toolkit.Internals.PointerEventArgs;
+using Syncfusion.Maui.Toolkit.Internals;
+using Syncfusion.Maui.Toolkit.TextInputLayout;
+using Syncfusion.Maui.Toolkit.NumericEntry;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+using Syncfusion.Maui.Toolkit.EntryView;
+using System.Globalization;
+
+namespace Syncfusion.Maui.Toolkit.NumericUpDown
+{
+ ///
+ /// The class allows users to adjust numeric values
+ /// using increment and decrement buttons or by direct input. It supports value range
+ /// constraints, step size, and culture-specific formatting.
+ ///
+ ///
+ /// The below example demonstrates how to initialize the .
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+
+ public partial class SfNumericUpDown : SfNumericEntry, ITextInputLayout, IParentThemeElement
+ {
+
+ #region Fields
+
+ ///
+ /// Gets or sets the right margin of the document.
+ ///
+ double _rightMargin;
+
+ ///
+ /// Gets or sets the left margin of the document.
+ ///
+ double _leftMargin;
+
+ ///
+ /// Timer for handling long press events.
+ ///
+ bool _isPressOccurring = false;
+
+ ///
+ /// Color of the up button.
+ ///
+ Color _upButtonColor = Colors.Black;
+
+ ///
+ /// Color of the down button.
+ ///
+ Color _downButtonColor = Colors.Black;
+
+ ///
+ /// Represents the point where the touch event occurs.
+ ///
+ Point _touchPoint;
+
+ ///
+ /// Size of the up/down buttons in pixels.
+ ///
+ const int UpDownButtonSize = 28;
+
+ ///
+ /// Padding around the buttons in pixels.
+ ///
+ const int ButtonPadding = 2;
+
+ ///
+ /// Holds the view for the up button, used to increase the value.
+ ///
+ View? _upButtonView;
+
+ ///
+ /// Holds the view for the down button, used to decrease the value.
+ ///
+ View? _downButtonView;
+
+ ///
+ /// List of semantic nodes for numeric entry.
+ ///
+ readonly List _numericUpDownSemanticsNodes = [];
+
+ ///
+ /// Size for the semantics of the down button.
+ ///
+ Size _downButtonSemanticsSize = Size.Zero;
+
+ ///
+ /// Size for the semantics of the up button.
+ ///
+ Size _upButtonSemanticsSize = Size.Zero;
+
+ ///
+ /// Rectangle for the down button's area.
+ ///
+ RectF _downButtonRectF;
+
+ ///
+ /// Rectangle for the up button's area.
+ ///
+ RectF _upButtonRectF;
+
+ ///
+ /// Temporary X position for the up/down buttons.
+ ///
+ float _tempUpDownX;
+
+ #endregion
+
+ #region Bindiable Properties
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty AutoReverseProperty =
+ BindableProperty.Create(
+ nameof(AutoReverse),
+ typeof(bool),
+ typeof(SfNumericEntry),
+ false,
+ BindingMode.Default,
+ null,
+ null);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty UpDownPlacementModeProperty =
+ BindableProperty.Create(
+ nameof(UpDownPlacementMode),
+ typeof(NumericUpDownPlacementMode),
+ typeof(SfNumericUpDown),
+ NumericUpDownPlacementMode.Inline,
+ BindingMode.Default,
+ propertyChanged: OnSpinButtonPlacementChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty UpDownButtonColorProperty =
+ BindableProperty.Create(nameof(UpDownButtonColor),
+ typeof(Color),
+ typeof(SfNumericUpDown),
+ Colors.Black,
+ BindingMode.Default,
+ propertyChanged: OnUpDownButtonColorPropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty DownButtonTemplateProperty =
+ BindableProperty.Create(
+ nameof(DownButtonTemplate),
+ typeof(DataTemplate),
+ typeof(SfNumericUpDown),
+ null,
+ BindingMode.Default,
+ propertyChanged: OnDownButtonTemplatePropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty UpButtonTemplateProperty =
+ BindableProperty.Create(
+ nameof(UpButtonTemplate),
+ typeof(DataTemplate),
+ typeof(SfNumericUpDown),
+ null,
+ BindingMode.Default,
+ propertyChanged: OnUpButtonTemplatePropertyChanged);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty SmallChangeProperty =
+ BindableProperty.Create(
+ nameof(SmallChange),
+ typeof(double),
+ typeof(SfNumericUpDown),
+ 1.0,
+ BindingMode.Default);
+
+ ///
+ /// Identifies dependency property.
+ ///
+ /// The identifier for the bindable property.
+ public static readonly BindableProperty LargeChangeProperty =
+ BindableProperty.Create(
+ nameof(LargeChange),
+ typeof(double),
+ typeof(SfNumericUpDown),
+ 10.0,
+ BindingMode.Default);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ public static readonly BindableProperty UpDownButtonAlignmentProperty =
+ BindableProperty.Create(
+ nameof(UpDownButtonAlignment),
+ typeof(UpDownButtonAlignment),
+ typeof(SfNumericUpDown),
+ UpDownButtonAlignment.Right,
+ propertyChanged: OnUpDownButtonAlignmentChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ internal static readonly BindableProperty UpDownButtonDisableColorProperty =
+ BindableProperty.Create(nameof(UpDownButtonDisableColor),
+ typeof(Color),
+ typeof(SfNumericUpDown),
+ Color.FromArgb("#611c1b1f"),
+ BindingMode.Default,
+ propertyChanged: OnUpDownButtonColorPropertyChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the SfNumericUpDown class.
+ /// This control allows users to select a numeric value using up and down buttons.
+ ///
+ public SfNumericUpDown() : base(true)
+ {
+ ThemeElement.InitializeThemeResources(this, "SfNumericUpDownTheme");
+ this.AddTouchListener(this);
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a value indicating whether to enable or disable the cyclic behavior when the value reaches the minimum or maximum value.
+ ///
+ /// The default value is false.
+ ///
+ /// Below is an example of how to configure the property using XAML and C#:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// ***
+ ///
+ public bool AutoReverse
+ {
+ get { return (bool)GetValue(AutoReverseProperty); }
+ set { SetValue(AutoReverseProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the up and down buttons in the control.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ /// To set the up and down button color to blue in XAML:
+ ///
+ /// ]]>
+ /// To set the up and down button color to blue in C#:
+ ///
+ ///
+ public Color UpDownButtonColor
+ {
+ get { return (Color)GetValue(UpDownButtonColorProperty); }
+ set { SetValue(UpDownButtonColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a Data template for down button.
+ ///
+ ///
+ /// The default value is null.
+ ///
+ ///
+ /// Below is an example of how to define a custom template for the down button:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// {
+ /// Grid grid = new Grid();
+ /// Label label = new Label
+ /// {
+ /// Text = "-",
+ /// HorizontalOptions = LayoutOptions.Center,
+ /// VerticalOptions = LayoutOptions.Center,
+ /// FontSize = 16,
+ /// FontAttributes = FontAttributes.Bold
+ /// };
+ /// grid.Children.Add(label);
+ /// return grid;
+ /// });
+ /// ]]>
+ /// ***
+ ///
+ public DataTemplate DownButtonTemplate
+ {
+ get { return (DataTemplate)GetValue(DownButtonTemplateProperty); }
+ set { SetValue(DownButtonTemplateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a Data template for up button.
+ ///
+ ///
+ /// The default value is null.
+ ///
+ /// ///
+ /// Below is an example of how to define a custom template for the up button:
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// {
+ /// Grid grid = new Grid();
+ /// Label label = new Label
+ /// {
+ /// Text = "+",
+ /// HorizontalOptions = LayoutOptions.Center,
+ /// VerticalOptions = LayoutOptions.Center,
+ /// FontSize = 16,
+ /// FontAttributes = FontAttributes.Bold
+ /// };
+ /// grid.Children.Add(label);
+ /// return grid;
+ /// });
+ /// ]]>
+ /// ***
+ ///
+ public DataTemplate UpButtonTemplate
+ {
+ get { return (DataTemplate)GetValue(UpButtonTemplateProperty); }
+ set { SetValue(UpButtonTemplateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that indicates the placement of buttons used to increment or decrement the property.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ /// To set the spin buttons to vertical in XAML:
+ ///
+ /// ]]>
+ /// To set the spin buttons to vertical in C#:
+ ///
+ ///
+ public NumericUpDownPlacementMode UpDownPlacementMode
+ {
+ get { return (NumericUpDownPlacementMode)GetValue(UpDownPlacementModeProperty); }
+ set { SetValue(UpDownPlacementModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that is added to or subtracted from when a small change is made, such as with an arrow key or scrolling.
+ ///
+ ///
+ /// The default value is 1.
+ ///
+ ///
+ /// To set a small change value of 0.1 in XAML:
+ ///
+ /// ]]>
+ /// To set a small change value of 0.1 in C#:
+ ///
+ ///
+ public double SmallChange
+ {
+ get { return (double)GetValue(SmallChangeProperty); }
+ set { SetValue(SmallChangeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that is added to or subtracted from when a large change is made, such as with the PageUP and PageDown keys.
+ ///
+ ///
+ /// The default value is 10.
+ ///
+ ///
+ /// To set a large change value of 5 in XAML:
+ ///
+ /// ]]>
+ /// To set a large change value of 5 in C#:
+ ///
+ ///
+ public double LargeChange
+ {
+ get { return (double)GetValue(LargeChangeProperty); }
+ set { SetValue(LargeChangeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the alignment of the buttons used to align increment or decrement the property.
+ ///
+ /// The default value is Right.
+ ///
+ /// To set a large change value of 5 in XAML:
+ ///
+ /// ]]>
+ /// To set a large change value of 5 in C#:
+ ///
+ ///
+ ///
+ /// In mode, if is set to Both.
+ /// The "UpDown" buttons is aligned to the right side.
+ ///
+ public UpDownButtonAlignment UpDownButtonAlignment
+ {
+ get { return (UpDownButtonAlignment)GetValue(UpDownButtonAlignmentProperty); }
+ set { SetValue(UpDownButtonAlignmentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the up and down buttons when they are disabled in the control.
+ ///
+ internal Color UpDownButtonDisableColor
+ {
+ get { return (Color)GetValue(UpDownButtonDisableColorProperty); }
+ set { SetValue(UpDownButtonDisableColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Property Changed
+
+ ///
+ /// Occurs when spin button placement property is changed.
+ ///
+ static void OnSpinButtonPlacementChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericUpDown numericUpDown)
+ {
+
+ SfNumericUpDown.UpdateSpinButtonPlacement(numericUpDown);
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnDownButtonTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericUpDown numericUpDown)
+ {
+ numericUpDown.AddDownButtonTemplate((DataTemplate)newValue);
+ numericUpDown.InvalidateDrawable();
+ numericUpDown.UpdateTextInputLayoutUI();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnUpButtonTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericUpDown numericUpDown)
+ {
+ numericUpDown.AddUpButtonTemplate((DataTemplate)newValue);
+ numericUpDown.InvalidateDrawable();
+ numericUpDown.UpdateTextInputLayoutUI();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnUpDownButtonColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericUpDown numericUpDown)
+ {
+ numericUpDown.UpdateButtonColor(numericUpDown.Value);
+ numericUpDown.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoke when UpDownButtonAlignment property is changed.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ private static void OnUpDownButtonAlignmentChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfNumericUpDown numericUpDown)
+ {
+ SfNumericUpDown.UpdateSpinButtonPlacement(numericUpDown);
+ numericUpDown.InvalidateDrawable();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Updates the current touch point's position based on the provided touch event data.
+ /// Adjusts the touch point for RTL (right-to-left) layouts if necessary.
+ ///
+ /// The touch point on the screen.
+ void UpdateTouchPoint(Point touch)
+ {
+#if WINDOWS
+ _touchPoint = IsRTL() ? new Point(Width - touch.X, touch.Y) : touch;
+#else
+ _touchPoint = touch;
+#endif
+ }
+
+ ///
+ /// Handles touch events for the control.
+ ///
+ /// The event data for the touch event, containing information about the pointer's position, state, and other relevant details.
+ public override void OnTouch(PointerEventArgs e)
+ {
+ if (IsTextInputLayout)
+ { return; }
+ UpdateTouchPoint(e.TouchPoint);
+
+ switch (e.Action)
+ {
+ case PointerActions.Pressed:
+ HandlePressed(e);
+ break;
+ case PointerActions.Released:
+ case PointerActions.Cancelled:
+ case PointerActions.Exited:
+ HandleReleasedOrCancelled(e);
+ break;
+ }
+ }
+
+ ///
+ /// Handles the logic when a touch press is detected. This includes handling
+ /// button presses and starting a long-press timer if applicable.
+ ///
+ /// The event arguments containing details about the touch press interaction.
+ void HandlePressed(PointerEventArgs e)
+ {
+#if WINDOWS
+ if (_textBox != null && _textBox.IsFocused)
+ {
+ _isFocus = true;
+ }
+#endif
+ HandleButtonPress(e);
+ }
+
+ ///
+ /// Applying measure updates for button visibility based on spins and up or down placement settings.
+ ///
+ static void UpdateSpinButtonPlacement(SfNumericUpDown numericUpDown)
+ {
+ numericUpDown.AddUpButtonTemplate(numericUpDown.UpButtonTemplate);
+ numericUpDown.AddDownButtonTemplate(numericUpDown.DownButtonTemplate);
+ VisualStateManager.GoToState(numericUpDown, numericUpDown.UpDownPlacementMode.ToString());
+ numericUpDown.UpdateEntryVisibility();
+ numericUpDown.InvalidateMeasure();
+ numericUpDown.UpdateTextInputLayoutUI();
+ numericUpDown.GetMinimumSize();
+ }
+
+ ///
+ /// Configures the layout for the up button, setting its position and size within the control.
+ ///
+ void SetUpButtonLayout()
+ {
+ if (UpButtonTemplate == null || _upButtonView == null)
+ {
+ return;
+ }
+
+ float xPosition;
+
+ if (UpDownPlacementMode == NumericUpDownPlacementMode.InlineVertical)
+ {
+ xPosition = IsRTL() ? _tempUpDownX : _upButtonRectF.X;
+ _upButtonView.Measure(_upButtonRectF.Width, _upButtonRectF.Height);
+ AbsoluteLayout.SetLayoutBounds(_upButtonView, new RectF(xPosition, _upButtonRectF.Y, _upButtonRectF.Width, _upButtonRectF.Height));
+ }
+ else
+ {
+ xPosition = IsRTL()
+ ? (UpDownPlacementMode == NumericUpDownPlacementMode.Inline ? UpDownButtonAlignment == UpDownButtonAlignment.Left
+ ? _tempUpDownX :
+ _tempUpDownX - ButtonSize : _tempUpDownX)
+ : _downButtonRectF.X;
+
+ if (UpDownButtonAlignment==UpDownButtonAlignment.Both && IsRTL())
+ {
+ xPosition = 4;
+ }
+ _upButtonView.Measure(_downButtonRectF.Width, _downButtonRectF.Height);
+ AbsoluteLayout.SetLayoutBounds(_upButtonView, new RectF(xPosition, _downButtonRectF.Y, _downButtonRectF.Width, _downButtonRectF.Height));
+ }
+ }
+
+ ///
+ /// Configures the layout for the down button, setting its position and size within the control.
+ ///
+ void SetDownButtonLayout()
+ {
+ if (DownButtonTemplate == null || _downButtonView == null)
+ {
+ return;
+ }
+
+ float xPosition;
+
+ if (IsInlineVerticalPlacement())
+ {
+ _downButtonView.Measure(_downButtonRectF.Width, _downButtonRectF.Height);
+
+ xPosition = IsRTL()
+ ? (IsInlinePlacement() ? _tempUpDownX - ButtonSize : _tempUpDownX)
+ : _downButtonRectF.X;
+
+ AbsoluteLayout.SetLayoutBounds(_downButtonView, new RectF(xPosition, _downButtonRectF.Y, _downButtonRectF.Width, _downButtonRectF.Height));
+ }
+ else
+ {
+ _downButtonView.Measure(_upButtonRectF.Width, _upButtonRectF.Height);
+
+ xPosition = IsRTL() ? UpDownButtonAlignment == UpDownButtonAlignment.Left
+ ? _tempUpDownX + ButtonSize : _tempUpDownX : _upButtonRectF.X;
+ AbsoluteLayout.SetLayoutBounds(_downButtonView, new RectF(xPosition, _upButtonRectF.Y, _upButtonRectF.Width, _upButtonRectF.Height));
+ }
+ }
+
+ ///
+ /// Draws the up and down buttons on the provided canvas.
+ ///
+ /// The canvas on which the buttons are drawn.
+ void DrawButtons(ICanvas canvas)
+ {
+ bool customUpButton = UpButtonTemplate != null && _upButtonView != null && IsInlinePlacement();
+ bool customDownButton = DownButtonTemplate != null && _downButtonView != null && IsInlinePlacement();
+
+ // Set layout for the up button or draw default up button
+ if (customUpButton)
+ {
+ SetUpButtonLayout();
+ }
+ else
+ {
+ RemoveExistingUporDownButtonView(_upButtonView);
+ DrawUpButton(canvas);
+ }
+
+ // Set layout for the down button or draw default down button
+ if (customDownButton)
+ {
+ SetDownButtonLayout();
+ }
+ else
+ {
+ RemoveExistingUporDownButtonView(_downButtonView);
+ DrawDownButton(canvas);
+ }
+ }
+
+ ///
+ /// Draws the up button on the specified canvas.
+ ///
+ /// The canvas on which the up button is drawn.
+ void DrawUpButton(ICanvas canvas)
+ {
+ var upButton = IsInlineVerticalPlacement() ? _upButtonRectF : _downButtonRectF;
+ canvas.StrokeColor = _upButtonColor;
+ canvas.FillColor = _upButtonColor;
+ _entryRenderer?.DrawUpButton(canvas, upButton);
+ }
+
+ ///
+ /// Draws the down button on the specified canvas.
+ ///
+ /// The canvas on which the down button is drawn.
+ void DrawDownButton(ICanvas canvas)
+ {
+ var downButton = IsInlineVerticalPlacement() ? _downButtonRectF : _upButtonRectF;
+ canvas.StrokeColor = _downButtonColor;
+ canvas.FillColor = _downButtonColor;
+ _entryRenderer?.DrawDownButton(canvas, downButton);
+ }
+
+ ///
+ /// Draws the entry user interface elements within the specified rectangle on the given canvas.
+ ///
+ /// The canvas where the entry UI elements are drawn.
+ /// The rectangle defining the area to be updated and redrawn.
+ internal override void DrawEntryUI(ICanvas canvas, Rect dirtyRect)
+ {
+ if (_textBox == null || IsTextInputLayout)
+ {
+ return;
+ }
+
+ if (_entryRenderer != null)
+ {
+ DrawButtons(canvas);
+ }
+ base.DrawEntryUI(canvas, dirtyRect);
+ }
+
+ ///
+ /// Updates the color of the button based on the provided value.
+ ///
+ /// A nullable double that determines the color change.
+ /// If null, the button may revert to a default color.
+ internal override void UpdateButtonColor(double? value)
+ {
+ if (value == Minimum)
+ {
+ ChangeButtonColors(UpDownButtonDisableColor, UpDownButtonColor, ValueStates.Minimum);
+ }
+ else if (value == Maximum)
+ {
+ ChangeButtonColors(UpDownButtonColor, UpDownButtonDisableColor, ValueStates.Maximum);
+ }
+ else
+ {
+ ChangeButtonColors(UpDownButtonColor, UpDownButtonColor, ValueStates.Normal);
+ }
+ UpdateTextInputLayoutUI();
+ UpdateEffectsRendererBounds();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Updates the bounds of the UI elements based on the specified rectangle.
+ ///
+ /// The rectangle defining the new bounds for the elements.
+ /// A boolean indicating whether to override existing bounds.
+ /// Defaults to false, meaning existing bounds will be preserved unless specified.
+ internal override void UpdateElementsBounds(RectF bounds, bool isOverride = false)
+ {
+ if (!IsTextInputLayout)
+ {
+ switch (UpDownPlacementMode)
+ {
+ case NumericUpDownPlacementMode.Inline:
+ ConfigureInlineButtonPositions(bounds);
+ break;
+ case NumericUpDownPlacementMode.InlineVertical:
+ ConfigureVerticalButtonPositions(bounds);
+ break;
+ }
+ }
+ ConfigureClearButton(bounds);
+ SetTextBoxMargin();
+ _numericUpDownSemanticsNodes.Clear();
+ InvalidateSemantics();
+ base.UpdateElementsBounds(bounds, true);
+ }
+
+ ///
+ /// Sets the margin for the text box within the numeric entry control based on its layout requirements.
+ ///
+ void SetTextBoxMargin()
+ {
+ if (_textBox != null)
+ {
+ _textBox.ButtonSize = ButtonSize + 4;
+ int defaultMargin = IsTextInputLayout ? 0 : 10;
+
+#if ANDROID || IOS || MACCATALYST
+ _textBox.Margin = new Thickness(_leftMargin + defaultMargin, 0, _rightMargin, 0);
+#else
+ _textBox.Margin = new Thickness(_leftMargin, 0, _rightMargin, 0);
+#endif
+ }
+ }
+
+ ///
+ /// Updates the size of the buttons used in the control, adjusting them to the specified size.
+ ///
+ /// The new size to which the buttons should be set, typically in device-independent units.
+ void UpdateButtonSize(float size)
+ {
+ _upButtonRectF.Width = _downButtonRectF.Width = size;
+ _upButtonRectF.Height = _downButtonRectF.Height = size;
+ }
+
+ ///
+ /// Configures the vertical positioning of buttons within the given bounding rectangle.
+ ///
+ /// The bounding rectangle that defines the available space for button placement.
+ void ConfigureVerticalButtonPositions(RectF bounds)
+ {
+ float xOffset = bounds.X + bounds.Width - UpDownButtonSize;
+#if ANDROID
+ xOffset -= 4;
+#endif
+
+ _upButtonRectF.X = IsRTL() ? 4 : xOffset;
+ _downButtonRectF.X = _upButtonRectF.X;
+#if !ANDROID
+ if (IsRTL())
+ {
+ _downButtonRectF.X = _upButtonRectF.X = 0;
+ }
+#endif
+ if (IsRTL())
+ {
+ _tempUpDownX = xOffset;
+ }
+
+ if (UpDownButtonAlignment==UpDownButtonAlignment.Left)
+ {
+ _upButtonRectF.X = IsRTL() ? xOffset : 4;
+ _downButtonRectF.X = _upButtonRectF.X;
+ if (IsRTL())
+ {
+ _tempUpDownX = 4;
+ }
+ }
+ _upButtonRectF.Y = bounds.Center.Y - (UpDownButtonSize * 0.75f) - ButtonPadding;
+ _downButtonRectF.Y = bounds.Center.Y - (UpDownButtonSize * 0.25f) + ButtonPadding;
+
+ UpdateButtonSize(UpDownButtonSize);
+ }
+
+ ///
+ /// Configures the inline (horizontal) positioning of buttons within the given bounding rectangle.
+ ///
+ /// The bounding rectangle that defines the available space for button placement.
+ void ConfigureInlineButtonPositions(RectF bounds)
+ {
+ float xOffset = bounds.X + bounds.Width - ButtonSize;
+#if ANDROID
+ xOffset -= 4;
+#endif
+
+ _upButtonRectF.X = IsRTL() ? 4 : xOffset;
+ _downButtonRectF.X = IsRTL() ? _upButtonRectF.X + ButtonSize : _upButtonRectF.X - ButtonSize;
+ _upButtonRectF.Y = _downButtonRectF.Y = bounds.Center.Y - (ButtonSize / 2);
+
+ switch (UpDownButtonAlignment)
+ {
+ case UpDownButtonAlignment.Left:
+ _downButtonRectF.X = IsRTL() ? xOffset : 4;
+ _upButtonRectF.X = IsRTL() ? _downButtonRectF.X - ButtonSize : _downButtonRectF.X + ButtonSize;
+ if (IsRTL())
+ {
+ _tempUpDownX = 4 ;
+ }
+ break;
+ case UpDownButtonAlignment.Right:
+ _upButtonRectF.X = IsRTL() ? 4 : xOffset;
+ _downButtonRectF.X = IsRTL() ? _upButtonRectF.X + ButtonSize : _upButtonRectF.X - ButtonSize;
+ if (IsRTL())
+ {
+ _tempUpDownX = xOffset;
+ }
+ break;
+ case UpDownButtonAlignment.Both:
+ _downButtonRectF.X = IsRTL() ? xOffset : 4;
+ _upButtonRectF.X = IsRTL() ? 4 : xOffset;
+ if (IsRTL())
+ {
+ _tempUpDownX = xOffset;
+ }
+ break;
+ }
+
+ UpdateButtonSize(UpDownButtonSize);
+ }
+
+ ///
+ /// Updates the bounds of the effects renderer to ensure proper rendering of visual effects.
+ ///
+ internal override void UpdateEffectsRendererBounds()
+ {
+ base.UpdateEffectsRendererBounds();
+ if (_valueStates != ValueStates.Minimum)
+ {
+ UpdateEffectsBounds( IsInlinePlacement() ? _upButtonRectF : _downButtonRectF);
+ }
+
+ if (_valueStates != ValueStates.Maximum)
+ {
+ UpdateEffectsBounds(IsInlinePlacement() ? _downButtonRectF : _upButtonRectF);
+ }
+ }
+
+ ///
+ /// Changes the colors of the up and down buttons based on the specified colors and state.
+ ///
+ /// The color to apply to the down button.
+ /// The color to apply to the up button.
+ /// The current state of the buttons which might dictate how colors are applied (e.g., default, pressed).
+ void ChangeButtonColors(Color downColor, Color upColor, ValueStates state)
+ {
+ _downButtonColor = downColor;
+ _upButtonColor = upColor;
+ _valueStates = state;
+ }
+
+ ///
+ /// Checks whether the current semantic data is still valid and does not need rebuilding.
+ ///
+ /// True if the semantic data is current, otherwise false.
+ bool SemanticsDataIsCurrent()
+ {
+ return _numericUpDownSemanticsNodes.Count != 0 &&
+ (_clearButtonSemanticsSize == new Size(_clearButtonRectF.Width, _clearButtonRectF.Height) ||
+ _downButtonSemanticsSize == new Size(_downButtonRectF.Width, _downButtonRectF.Height) ||
+ _upButtonSemanticsSize == new Size(_upButtonRectF.Width, _upButtonRectF.Height));
+ }
+
+ ///
+ /// Method called when the control's parent changes, allowing for adjustments related to the change in hierarchy or parent container.
+ ///
+ protected override void OnParentChanged()
+ {
+ base.OnParentChanged();
+ OnNumericEntryParentChanged();
+ }
+
+ ///
+ /// Updates the semantic sizes for the clear, down, and up buttons
+ /// based on their current rectangle dimensions.
+ ///
+ void UpdateSemanticsSizes()
+ {
+ _clearButtonSemanticsSize = new Size(_clearButtonRectF.Width, _clearButtonRectF.Height);
+ _downButtonSemanticsSize = new Size(_downButtonRectF.Width, _downButtonRectF.Height);
+ _upButtonSemanticsSize = new Size(_upButtonRectF.Width, _upButtonRectF.Height);
+ }
+
+ ///
+ /// Adds a semantics node for a given element in the control.
+ ///
+ /// The rectangular bounds of the element.
+ /// The ID of the semantics node.
+ /// The text description for the node, used for accessibility.
+ void AddSemanticsNode(RectF bounds, int id, string description)
+ {
+ SemanticsNode node = new SemanticsNode
+ {
+ Id = id,
+ Bounds = new Rect(bounds.X, bounds.Y, bounds.Width, bounds.Height),
+ Text = $"{description} double tap to activate"
+ };
+ _numericUpDownSemanticsNodes.Add(node);
+ }
+
+ ///
+ /// Retrieves the list of semantics nodes for accessibility support,
+ /// which represent how the control is perceived by assistive technologies.
+ ///
+ /// The width constraint for the semantics nodes.
+ /// The height constraint for the semantics nodes.
+ /// A list of semantics nodes that describe the accessible elements within this control.
+ protected override List GetSemanticsNodesCore(double width, double height)
+ {
+ UpdateSemanticsSizes();
+
+ if (SemanticsDataIsCurrent())
+ {
+ return _numericUpDownSemanticsNodes;
+ }
+
+ if (_isClearButtonVisible && !IsTextInputLayout)
+ {
+ AddSemanticsNode(_clearButtonRectF, 1, "Clear button");
+ }
+
+ if (IsInlineVerticalPlacement() && !IsTextInputLayout)
+ {
+ AddSemanticsNode(_upButtonRectF, 2, "Up button");
+ AddSemanticsNode(_downButtonRectF, 3, "Down button");
+ }
+ if (IsInlinePlacement() && !IsTextInputLayout)
+ {
+ AddSemanticsNode(_downButtonRectF, 2, "Up button");
+ AddSemanticsNode(_upButtonRectF, 3, "Down button");
+ }
+
+ return _numericUpDownSemanticsNodes;
+ }
+
+ ///
+ /// Measures the content of the control to determine the desired size based on the given constraints.
+ ///
+ /// The maximum width available for the content.
+ /// The maximum height available for the content.
+ /// The desired size for the content, which should be within the given constraints.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ // Default to an empty size
+ Size measure = Size.Zero;
+
+ // Measure the textbox if it exists
+ if (_textBox != null)
+ {
+ measure = _textBox.Measure(widthConstraint, heightConstraint);
+ }
+
+ // Adjust width for inline button placement
+ if (UpDownPlacementMode == NumericUpDownPlacementMode.Inline)
+ {
+ measure.Width += 2 * ButtonSize;
+ }
+
+ // Calculate measured width considering constraints
+ double measuredWidth = double.IsInfinity(widthConstraint) ? measure.Width : Math.Min(measure.Width, widthConstraint);
+
+ // Calculate measured height considering constraints
+ double measuredHeight = double.IsInfinity(heightConstraint) ? measure.Height : Math.Min(measure.Height, heightConstraint);
+
+ // Ensure minimum height if EntryVisibility is Collapsed
+ if (EntryVisibility == Visibility.Collapsed)
+ {
+ measuredHeight = Math.Max(measuredHeight, ButtonSize);
+ }
+
+ return new Size(measuredWidth, measuredHeight);
+ }
+
+ internal override void GetMinimumSize()
+ {
+ if (IsTextInputLayout)
+ {
+ return;
+ }
+
+ // Calculate minimum height based on placement mode
+ MinimumHeightRequest = UpDownPlacementMode == NumericUpDownPlacementMode.InlineVertical
+ ? SfNumericUpDown.DetermineMinimumHeightForVerticalPlacement()
+ : SfNumericUpDown.DetermineMinimumHeightForInlinePlacement();
+
+ // Set minimum width request commonly for both placement modes
+ MinimumWidthRequest = 2 * ButtonSize;
+ }
+
+ private static double DetermineMinimumHeightForVerticalPlacement()
+ {
+ // Use platform-specific directives only if necessary
+ #if !ANDROID
+ return (2 * UpDownButtonSize) - (UpDownButtonSize / 3);
+ #else
+ return 2 * UpDownButtonSize;
+ #endif
+ }
+
+ private static double DetermineMinimumHeightForInlinePlacement()
+ {
+ // For inline placement, return height based on button size
+ return ButtonSize;
+ }
+
+ internal override void UpdateTextInputLayoutUI()
+ {
+ base.UpdateTextInputLayoutUI();
+ if (_textInputLayout != null)
+ {
+ _textInputLayout.ShowUpDownButton = true;
+ _textInputLayout.IsUpDownVerticalAlignment = (UpDownPlacementMode == NumericUpDownPlacementMode.InlineVertical);
+ _textInputLayout.IsUpDownAlignmentLeft = (UpDownButtonAlignment == UpDownButtonAlignment.Left);
+ _textInputLayout.IsUpDownAlignmentBoth = (UpDownButtonAlignment == UpDownButtonAlignment.Both);
+ OnNumericEntryParentChanged();
+ _textInputLayout.DownButtonColor = _downButtonColor;
+ _textInputLayout.UpButtonColor = _upButtonColor;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the placement mode is set to Inline.
+ ///
+ ///
+ /// true if the placement mode is Inline; otherwise, false.
+ ///
+ bool IsInlinePlacement()
+ {
+ return UpDownPlacementMode == NumericUpDownPlacementMode.Inline;
+ }
+
+ ///
+ /// Gets a value indicating whether the placement mode is set to InlineVertical.
+ ///
+ ///
+ /// true if the placement mode is InlineVertical; otherwise, false.
+ ///
+ bool IsInlineVerticalPlacement()
+ {
+ return UpDownPlacementMode == NumericUpDownPlacementMode.InlineVertical;
+ }
+
+ ///
+ /// Update the textbox visibility based on EntryVisibility property.
+ ///
+ void UpdateEntryVisibility()
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+
+ switch (EntryVisibility)
+ {
+ case Visibility.Collapsed:
+ UpdateEntryVisualStates(false, "EntryCollapsed");
+ break;
+ case Visibility.Hidden:
+ UpdateEntryVisualStates(false, "EntryHidden");
+ break;
+ default:
+ UpdateEntryVisualStates(true, "EntryVisible");
+ break;
+ }
+ }
+
+ ///
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfNumericUpDownStyles();
+ }
+
+ ///
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ SetDynamicResource(UpDownButtonDisableColorProperty, "SfNumericUpDownDisabledArrowColor");
+ }
+
+ ///
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+
+ }
+
+ ///
+ /// Start the long press timer
+ ///
+ async void StartPressTimer()
+ {
+ _isPressOccurring = true;
+ await Task.Delay(500);
+ await SfNumericUpDown.RecursivePressedAsync(this);
+ }
+
+ ///
+ /// Recursively call the increment and decrement value
+ ///
+ /// The numericupdown instance
+ ///
+ static async Task RecursivePressedAsync(SfNumericUpDown numericUpdown)
+ {
+ if (!numericUpdown._isPressOccurring)
+ {
+ return;
+ }
+
+ numericUpdown.HandleUpDownPressed();
+ // Wait for the specified delay
+ await Task.Delay(200);
+
+ // Call the method recursively
+ await SfNumericUpDown.RecursivePressedAsync(numericUpdown);
+ }
+
+ ///
+ /// Method that handles the logic when the clear button is tapped.
+ ///
+ void OnClearButtonTouchUp()
+ {
+ UpdateDisplayText(null);
+ if (IsDescriptionNotSetByUser)
+ {
+ SemanticProperties.SetDescription(this, "Clear button pressed");
+ }
+ SemanticScreenReader.Announce(SemanticProperties.GetDescription(this));
+ _numericUpDownSemanticsNodes.Clear();
+ InvalidateSemantics();
+ }
+
+ ///
+ /// Handles the logic when a touch release or cancellation is detected, stopping
+ /// the long-press timer and triggering release-related actions.
+ ///
+ /// The event arguments detailing the touch release or cancel interaction.
+ void HandleReleasedOrCancelled(PointerEventArgs e)
+ {
+ StopPressTimer();
+ if (e.Action == PointerActions.Released)
+ {
+ if (ClearButtonClicked(_touchPoint))
+ {
+ OnClearButtonTouchUp();
+ }
+ ClearHighlightIfNecessary();
+ }
+ }
+
+ ///
+ /// Stops the long press timer if it is not null.
+ ///
+ private void StopPressTimer()
+ {
+ _isPressOccurring = false;
+ }
+
+ ///
+ /// Checks whether to execute increase command.
+ ///
+ bool CanIncrease()
+ {
+ return Value == null || double.IsNaN((double)Value) || Value < Maximum || AutoReverse;
+
+ }
+
+ ///
+ /// Increase the value by small change value.
+ ///
+ void Increase()
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+ if (_textBox.IsFocused == false)
+ {
+ // When control is unfocused, the display text is formatted and may contain symbols as well as a restricted number of fraction digits.
+ // As a result, we should increase the value rather than the display text;
+ // otherwise, the fraction values would be lost, and we will be unable to convert text into value.
+ IncreaseValue((decimal)SmallChange);
+ }
+ else
+ {
+ // When a control has focus, the display text is unformatted and it contains only numeric values,
+ // also its value may differ from the value property.As a result, we should increase the current display text value.
+ IncreaseDisplayText((decimal)SmallChange);
+ UpdateValue();
+ }
+ }
+
+ ///
+ /// Handles the logic when the up button is pressed, triggering an increase in value
+ /// if applicable, and updating semantic properties.
+ ///
+ /// The event arguments for the up button press interaction.
+ void OnUpButtonPressed(PointerEventArgs e)
+ {
+ if (_textBox is not null && CanIncrease())
+ {
+ Increase();
+ }
+
+ AnnounceButtonPress("Up button pressed");
+ }
+
+ ///
+ /// Applies auto-reverse logic on the given value.
+ ///
+ /// The value to adjust.
+ void ApplyAutoReverse(ref decimal value)
+ {
+ if (AutoReverse)
+ {
+ if (Maximum != double.MaxValue && value > (decimal)Maximum)
+ {
+ value = (decimal)Minimum;
+ }
+ else if (Minimum != double.MinValue && value < (decimal)Minimum)
+ {
+ value = (decimal)Maximum;
+ }
+ }
+ }
+
+ ///
+ /// Increase the display text value by given value.
+ ///
+ void IncreaseDisplayText(decimal delta)
+ {
+ if (_textBox != null)
+ {
+ decimal value;
+ if (Culture != null)
+ {
+ value = decimal.TryParse(_textBox.Text, NumberStyles.Number, Culture, out value) ? value : 0;
+ }
+ else
+ {
+ value = decimal.TryParse(_textBox.Text, out value) ? value : 0;
+ }
+
+ value += delta;
+ ApplyAutoReverse(ref value);
+ _textBox.Text = RestrictFractionDigit(value.ToString(GetNumberFormat()));
+ _textBox.CursorPosition = _textBox.Text.Length;
+ }
+ }
+
+ ///
+ /// To get the NumberFormat from number formatter, culture property or current UI culture.
+ ///
+ NumberFormatInfo GetNumberFormat()
+ {
+ var applyCulture = Culture ?? CultureInfo.CurrentUICulture;
+ // Use custom format culture if specified
+ if (CustomFormat != null && Culture == null)
+ {
+ applyCulture = CultureInfo.CurrentUICulture;
+ }
+
+ return applyCulture.NumberFormat;
+ }
+
+ ///
+ /// Increase the value when clicking repeat button if control is unfocused.
+ ///
+ void IncreaseValue(decimal delta)
+ {
+ decimal value = decimal.TryParse(Value.ToString(), out value) ? value : 0;
+ value += delta;
+ ApplyAutoReverse(ref value);
+ SetValue(ValueProperty, Convert.ToDouble(value));
+ }
+
+ internal override bool HandleKeyPressed(KeyboardKey key)
+ {
+ if ((key == KeyboardKey.Up && CanIncrease()) ||
+ (key == KeyboardKey.Down && CanDecrease()))
+ {
+ var delta = key == KeyboardKey.Up ? SmallChange : -SmallChange;
+ IncreaseDisplayText((decimal)delta);
+ UpdateValue();
+ }
+
+ return true;
+ }
+
+ internal override bool HandleKeyPagePressed(KeyboardKey key)
+ {
+ if ((key == KeyboardKey.PageUp && CanIncrease()) ||
+ (key == KeyboardKey.PageDown && CanDecrease()))
+ {
+ var delta = key == KeyboardKey.PageUp ? LargeChange : -LargeChange;
+ IncreaseDisplayText((decimal)delta);
+ UpdateValue();
+ }
+
+ return true;
+
+ }
+
+ ///
+ /// Checks whether to execute decrease command.
+ ///
+ bool CanDecrease()
+ {
+ return Value == null || double.IsNaN((double)Value) || Value > Minimum || AutoReverse;
+ }
+
+ ///
+ /// Handles the event when the down button is pressed.
+ ///
+ /// The pointer event arguments.
+ void OnDownButtonPressed(PointerEventArgs e)
+ {
+ if (_textBox != null && CanDecrease())
+ {
+ Decrease();
+ }
+ AnnounceButtonPress("Down button pressed");
+ }
+
+ ///
+ /// Announces a button press using the semantic screen reader.
+ ///
+ /// The message to announce.
+ void AnnounceButtonPress(string message)
+ {
+ if (IsDescriptionNotSetByUser)
+ {
+ SemanticProperties.SetDescription(this, message);
+ }
+ SemanticScreenReader.Announce(SemanticProperties.GetDescription(this));
+ }
+
+ ///
+ /// Decrease the value by small change value.
+ ///
+ void Decrease()
+ {
+ if (_textBox == null)
+ {
+ return;
+ }
+ if (_textBox.IsFocused == false)
+ {
+ // When a control has unfocused, First the value should be decreased then the display text value will be updated.
+ IncreaseValue((decimal)-SmallChange);
+ }
+ else
+ {
+ // When a control has focus, First the display text value should be decreased then the value will be updated.
+ IncreaseDisplayText((decimal)-SmallChange);
+ UpdateValue();
+ }
+ }
+
+#if WINDOWS
+
+ ///
+ /// Handles changes to the mouse wheel pointer, determining whether to initiate
+ /// an increase or decrease in the numeric value based on the wheel movement.
+ ///
+ /// The source of the event, typically the control itself.
+ /// Event arguments providing data for the pointer wheel event.
+ void OnPointerWheelChanged(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
+ {
+ if (e == null || _textBox == null || !_textBox.IsFocused)
+ {
+ return;
+ }
+
+ e.Handled = true;
+ double delta = e.GetCurrentPoint(_textBox.ToPlatform()).Properties.MouseWheelDelta;
+
+ ProcessWheelDelta(delta);
+ }
+
+ ///
+ /// Determines the direction of the wheel delta and calls the appropriate method to increase or decrease the numeric value.
+ ///
+ /// The amount of wheel movement. Positive values indicate upward movement; negative values indicate downward movement.
+ void ProcessWheelDelta(double delta)
+ {
+ if (delta > 0)
+ {
+ AttemptIncrease();
+ }
+ else if (delta < 0)
+ {
+ AttemptDecrease();
+ }
+ }
+
+
+ ///
+ /// Attempts to increase the numeric value by a small change amount.
+ ///
+ void AttemptIncrease()
+ {
+ if (CanIncrease())
+ {
+ IncreaseDisplayText((decimal)SmallChange);
+ UpdateValue();
+ }
+ }
+
+
+ ///
+ /// Attempts to decrease the numeric value by a small change amount.
+ ///
+ void AttemptDecrease()
+ {
+ if (CanDecrease())
+ {
+ IncreaseDisplayText((decimal)-SmallChange);
+ UpdateValue();
+ }
+ }
+#endif
+
+ ///
+ /// Handles changes to the TextBox control in a .NET MAUI application.
+ /// This method is triggered whenever the TextBox's state changes,
+ /// allowing for custom behavior in response to those changes.
+ ///
+ /// The source of the event, typically the TextBox.
+ /// Event arguments containing data related to the change event.
+ protected override void TextBox_HandlerChanged(object? sender, EventArgs e)
+ {
+ base.TextBox_HandlerChanged(sender, e);
+ if (sender is SfEntryView textBox)
+ {
+#if WINDOWS
+ if (textBox.Handler is not null && textBox.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox windowTextBox)
+ {
+ windowTextBox.PointerWheelChanged += OnPointerWheelChanged;
+ }
+#endif
+ }
+ }
+
+ ///
+ /// Handles the press event for the long press, typically triggering repeat actions such as incrementing or decrementing a value.
+ ///
+ void HandleUpDownPressed()
+ {
+ if (_textBox is null)
+ {
+ return;
+ }
+
+ bool isDownButtonPressed = _downButtonRectF.Contains(_touchPoint);
+ bool isVerticalPlacement = IsInlineVerticalPlacement();
+
+ if (isDownButtonPressed)
+ {
+ HandleDownButtonPress(isVerticalPlacement);
+ }
+ else if (_upButtonRectF.Contains(_touchPoint))
+ {
+ HandleUpButtonPress(isVerticalPlacement);
+ }
+ }
+
+ ///
+ /// Handles the logic when the down button is pressed, determining whether to increase or decrease the value
+ /// based on the button placement mode.
+ ///
+ /// A boolean indicating if the button placement is vertical.
+ void HandleDownButtonPress(bool isVerticalPlacement)
+ {
+ if (isVerticalPlacement)
+ {
+ TryDecrease();
+ }
+ else
+ {
+ TryIncrease();
+ }
+ }
+
+ ///
+ /// Handles the logic when the up button is pressed, determining whether to increase or decrease the value
+ /// based on the button placement mode.
+ ///
+ /// A boolean indicating if the button placement is vertical.
+ void HandleUpButtonPress(bool isVerticalPlacement)
+ {
+ if (isVerticalPlacement)
+ {
+ TryIncrease();
+ }
+ else
+ {
+ TryDecrease();
+ }
+ }
+
+ ///
+ /// Attempts to increase the numeric value, checking if the increase action is allowed.
+ ///
+ void TryIncrease()
+ {
+ if (CanIncrease())
+ {
+ Increase();
+ }
+ }
+
+ ///
+ /// Attempts to decrease the numeric value, checking if the decrease action is allowed.
+ ///
+ void TryDecrease()
+ {
+ if (CanDecrease())
+ {
+ Decrease();
+ }
+ }
+
+ internal override void DownButtonPressed()
+ {
+ if (_textBox != null && CanDecrease())
+ {
+ Decrease();
+ }
+ }
+
+ internal override void UpButtonPressed()
+ {
+ if (_textBox != null && CanIncrease())
+ {
+ Increase();
+ }
+ }
+
+ ///
+ /// Handles button press events, determining the necessary action based on the touch point's location
+ /// and the placement of the buttons.
+ ///
+ /// The event data associated with the button press.
+ void HandleButtonPress(PointerEventArgs e)
+ {
+ if (_downButtonRectF.Contains(_touchPoint))
+ {
+ if (IsInlineVerticalPlacement())
+ {
+ OnDownButtonPressed(e);
+ }
+ else
+ {
+ OnUpButtonPressed(e);
+ }
+ StartPressTimer();
+ }
+ else if (_upButtonRectF.Contains(_touchPoint))
+ {
+ if (IsInlineVerticalPlacement())
+ {
+ OnUpButtonPressed(e);
+ }
+ else
+ {
+ OnDownButtonPressed(e);
+ }
+ StartPressTimer();
+ }
+#if !WINDOWS
+ else
+ {
+ Focus();
+ }
+#endif
+ }
+
+ ///
+ /// Updates the visual states of the entry control, setting its visibility based on state name and the specified visibility parameter.
+ ///
+ /// A boolean indicating whether the entry should be visible.
+ /// The name of the state to apply to the entry.
+ void UpdateEntryVisualStates(bool visibility, string stateName)
+ {
+ if (_textBox != null)
+ {
+ VisualStateManager.GoToState(this, stateName);
+ _textBox.IsVisible = visibility;
+ }
+ }
+
+ ///
+ /// Removes the specified button view from the control if it exists within the child elements.
+ ///
+ /// The view of the button to be removed.
+ void RemoveExistingUporDownButtonView(View? buttonView)
+ {
+ if (buttonView != null && Children.Contains(buttonView))
+ {
+ Children.Remove(buttonView);
+ }
+ }
+
+ internal void OnNumericEntryParentChanged()
+ {
+ if (IsTextInputLayout && _upButtonView!=null)
+ {
+ RemoveExistingUporDownButtonView(_upButtonView);
+ }
+ if (IsTextInputLayout && _downButtonView != null)
+ {
+ RemoveExistingUporDownButtonView(_downButtonView);
+ }
+ }
+
+ ///
+ /// Adds a new button view using the provided data template.
+ ///
+ /// The button view reference to be set with the newly created view.
+ /// The data template used to create the new button view.
+ void AddNewButtonView(ref View? buttonView, DataTemplate newView)
+ {
+ if (newView == null)
+ {
+ return;
+ }
+
+ object layout = newView.CreateContent();
+ buttonView = ExtractViewFromLayout(layout);
+
+ if (buttonView != null)
+ {
+ SetupViewBinding(buttonView, "IsVisible");
+ if (!IsTextInputLayout)
+ {
+ Add(buttonView);
+ }
+ }
+ }
+
+ ///
+ /// Sets up a binding for the specified view, linking its 'IsVisible' property to a given path in its data context.
+ ///
+ /// The view to be bound.
+ /// The path of the property in the data context to bind to.
+ static void SetupViewBinding(View view, string path)
+ {
+ view.SetBinding(SfNumericEntry.IsVisibleProperty, new Binding
+ {
+ Source = view,
+ Path = path
+ });
+ }
+
+ ///
+ /// Extracts a view from the provided layout content, checking if the content is a ViewCell or a direct View.
+ ///
+ /// The layout content from which to extract the view.
+ /// The extracted View, or null if the content is not a view.
+ static View? ExtractViewFromLayout(object layout)
+ {
+ if (layout is ViewCell viewCell)
+ {
+ return viewCell.View;
+ }
+ return layout as View;
+ }
+
+ ///
+ /// Adds the up button template to the view.
+ ///
+ /// The new data template for the up button.
+ void AddUpButtonTemplate(DataTemplate newView)
+ {
+ RemoveExistingUporDownButtonView(_upButtonView);
+
+ if (UpButtonTemplate != null)
+ {
+ AddNewButtonView(ref _upButtonView, newView);
+ }
+ }
+
+ ///
+ /// Adds the down button template to the view.
+ ///
+ /// The new data template for the down button.
+ void AddDownButtonTemplate(DataTemplate newView)
+ {
+ RemoveExistingUporDownButtonView(_downButtonView);
+
+ if (UpButtonTemplate != null)
+ {
+ AddNewButtonView(ref _downButtonView, newView);
+ }
+ }
+
+ ///
+ /// Configures the positioning and size of the clear button within the given bounding rectangle.
+ ///
+ /// The bounding rectangle that defines the available space for positioning the clear button.
+ void ConfigureClearButton(RectF bounds)
+ {
+ _leftMargin = 0;
+ _rightMargin = 0;
+ if (!_isClearButtonVisible)
+ {
+ UpDateMargin();
+ return;
+ }
+
+ SetClearButtonPosition(bounds);
+ SetClearButtonSize();
+ UpdateTextBoxMargin();
+ }
+
+ ///
+ /// Updates the margin values for the control based on the placement mode and button alignment.
+ ///
+ void UpDateMargin()
+ {
+ if(IsTextInputLayout)
+ {
+ return;
+ }
+ if (UpDownPlacementMode == NumericUpDownPlacementMode.InlineVertical)
+ {
+ if (UpDownButtonAlignment == UpDownButtonAlignment.Left)
+ {
+ _leftMargin = ButtonSize;
+ _rightMargin = 0;
+ }
+ else if (UpDownButtonAlignment == UpDownButtonAlignment.Right || UpDownButtonAlignment == UpDownButtonAlignment.Both)
+ {
+ _rightMargin = ButtonSize;
+ _leftMargin = 0;
+ }
+ }
+ else if (UpDownPlacementMode == NumericUpDownPlacementMode.Inline)
+ {
+ if (UpDownButtonAlignment == UpDownButtonAlignment.Left)
+ {
+ _leftMargin = 2 * ButtonSize;
+ _rightMargin = 0;
+ }
+ else if (UpDownButtonAlignment == UpDownButtonAlignment.Right)
+ {
+ _rightMargin = 2 * ButtonSize;
+ _leftMargin = 0;
+ }
+ else
+ {
+ _leftMargin = ButtonSize;
+ _rightMargin = ButtonSize;
+ }
+ }
+ else
+ {
+ _leftMargin = _rightMargin = 0;
+ }
+ }
+
+ ///
+ /// Sets the position of the clear button within the control.
+ ///
+ /// The bounding rectangle defining the control's size and position.
+ void SetClearButtonPosition(RectF bounds)
+ {
+ bool rtl = IsRTL();
+
+ if (UpDownButtonAlignment == UpDownButtonAlignment.Left)
+ {
+ _clearButtonRectF.X = rtl ? (bounds.X) : (bounds.Width - ButtonSize);
+ }
+ else if (UpDownButtonAlignment == UpDownButtonAlignment.Right)
+ {
+ _clearButtonRectF.X = rtl ? (_downButtonRectF.X + ButtonSize) : (_downButtonRectF.X - ButtonSize);
+ }
+ else
+ {
+ _clearButtonRectF.X = rtl ? (_upButtonRectF.X + ButtonSize) : (_upButtonRectF.X - ButtonSize);
+ }
+ _clearButtonRectF.Y = bounds.Center.Y - (ButtonSize / 2);
+ }
+
+
+ ///
+ /// Sets the size of the clear button using the predefined button size.
+ ///
+ void SetClearButtonSize()
+ {
+ _clearButtonRectF.Width = ButtonSize;
+ _clearButtonRectF.Height = ButtonSize;
+ }
+
+ ///
+ /// Updates the left and right margins of the text box based on the current UpDownButtonAlignment.
+ ///
+ void UpdateTextBoxMargin()
+ {
+ if (IsTextInputLayout)
+ {
+ return;
+ }
+ switch (UpDownButtonAlignment)
+ {
+ case UpDownButtonAlignment.Left:
+ _leftMargin = IsInlinePlacement() ? ButtonSize * 2 : ButtonSize;
+ _rightMargin = ButtonSize;
+ break;
+ case UpDownButtonAlignment.Right:
+ _rightMargin = IsInlinePlacement() ? ButtonSize * 3 : ButtonSize * 2;
+ _leftMargin = 0;
+ break;
+ case UpDownButtonAlignment.Both:
+ _leftMargin = IsInlinePlacement() ? ButtonSize : 0 ;
+ _rightMargin = ButtonSize *2;
+ break;
+ }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/maui/src/TextInputLayout/ITextInputLayout.cs b/maui/src/TextInputLayout/ITextInputLayout.cs
new file mode 100644
index 00000000..2c190df1
--- /dev/null
+++ b/maui/src/TextInputLayout/ITextInputLayout.cs
@@ -0,0 +1,11 @@
+namespace Syncfusion.Maui.Toolkit.TextInputLayout
+{
+ interface ITextInputLayout
+ {
+ internal void ClearIconPressed();
+
+ internal void DownButtonPressed();
+
+ internal void UpButtonPressed();
+ }
+}
diff --git a/maui/src/TextInputLayout/LabelStyle.cs b/maui/src/TextInputLayout/LabelStyle.cs
index 26a13b4d..9d55a27a 100644
--- a/maui/src/TextInputLayout/LabelStyle.cs
+++ b/maui/src/TextInputLayout/LabelStyle.cs
@@ -2,253 +2,253 @@
namespace Syncfusion.Maui.Toolkit.TextInputLayout
{
- ///
- /// Represents the style for hint text, error text, and helper text label elements
- /// in the control. Provides customizable properties
- /// for text appearance.
- ///
- public class LabelStyle : Element, ITextElement
- {
- #region Bindable properties
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the color of the text in .
- ///
- public static readonly BindableProperty TextColorProperty =
- BindableProperty.Create(
- nameof(TextColor),
- typeof(Color),
- typeof(LabelStyle),
- Color.FromRgba(0, 0, 0, 0.87));
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the font size of the .
- ///
- public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property specifies the font family for the text in the .
- ///
- public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property specifies the font attributes for the text in the .
- ///
- public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether automatic font scaling is enabled in the .
- ///
- public static readonly BindableProperty FontAutoScalingEnabledProperty = FontElement.FontAutoScalingEnabledProperty;
-
- #endregion
-
- #region Properties
- ///
- /// Gets or sets the font size for the label.
- ///
- ///
- /// The following code demonstrates how to use the FontSize property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- [System.ComponentModel.TypeConverter(typeof(FontSizeConverter))]
- public double FontSize
- {
- get { return (double)GetValue(FontSizeProperty); }
- set { SetValue(FontSizeProperty, value); }
- }
-
- ///
- /// Gets or sets the font family for the label.
- ///
- ///
- /// The following code demonstrates how to use the FontFamily property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public string FontFamily
- {
- get { return (string)GetValue(FontFamilyProperty); }
- set { SetValue(FontFamilyProperty, value); }
- }
-
- ///
- /// Gets or sets the font attributes for the label.
- ///
- ///
- /// The following code demonstrates how to use the FontAttributes property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public FontAttributes FontAttributes
- {
- get { return (FontAttributes)GetValue(FontAttributesProperty); }
- set { SetValue(FontAttributesProperty, value); }
- }
-
- ///
- /// Gets or sets whether automatic font scaling is enabled.
- ///
- ///
- /// The following code demonstrates how to use the FontAutoScalingEnabled property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool FontAutoScalingEnabled
- {
- get { return (bool)GetValue(FontAutoScalingEnabledProperty); }
- set { SetValue(FontAutoScalingEnabledProperty, value); }
- }
-
- ///
- /// Gets or sets the text color for the label.
- ///
- /// Used for helper, hint, and counter labels. For error label color, use the Stroke property in error state.
- ///
- ///
- ///
- /// The following code demonstrates how to use the TextColor property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public Color TextColor
- {
- get { return (Color)GetValue(TextColorProperty); }
- set { SetValue(TextColorProperty, value); }
- }
-
- Microsoft.Maui.Font ITextElement.Font => (Microsoft.Maui.Font)GetValue(FontElement.FontProperty);
-
- #endregion
-
- #region Methods
-
- double ITextElement.FontSizeDefaultValueCreator()
- {
- return 12d;
- }
-
- void ITextElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue)
- {
-
- }
-
- void ITextElement.OnFontFamilyChanged(string oldValue, string newValue)
- {
-
- }
-
- void ITextElement.OnFontSizeChanged(double oldValue, double newValue)
- {
-
- }
-
- void ITextElement.OnFontChanged(Microsoft.Maui.Font oldValue, Microsoft.Maui.Font newValue)
- {
-
- }
-
- ///
- /// Called when the value changes.
- ///
- /// The old value of .
- /// The new value of .
- void ITextElement.OnFontAutoScalingEnabledChanged(bool oldValue, bool newValue)
- {
-
- }
-
- #endregion
- }
+ ///
+ /// Represents the style for hint text, error text, and helper text label elements
+ /// in the control. Provides customizable properties
+ /// for text appearance.
+ ///
+ public partial class LabelStyle : Element, ITextElement
+ {
+ #region Bindable properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the color of the text in .
+ ///
+ public static readonly BindableProperty TextColorProperty =
+ BindableProperty.Create(
+ nameof(TextColor),
+ typeof(Color),
+ typeof(LabelStyle),
+ Color.FromRgba(0, 0, 0, 0.87));
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the font size of the .
+ ///
+ public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property specifies the font family for the text in the .
+ ///
+ public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property specifies the font attributes for the text in the .
+ ///
+ public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether automatic font scaling is enabled in the .
+ ///
+ public static readonly BindableProperty FontAutoScalingEnabledProperty = FontElement.FontAutoScalingEnabledProperty;
+
+ #endregion
+
+ #region Properties
+ ///
+ /// Gets or sets the font size for the label.
+ ///
+ ///
+ /// The following code demonstrates how to use the FontSize property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ [System.ComponentModel.TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font family for the label.
+ ///
+ ///
+ /// The following code demonstrates how to use the FontFamily property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font attributes for the label.
+ ///
+ ///
+ /// The following code demonstrates how to use the FontAttributes property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ ///
+ /// Gets or sets whether automatic font scaling is enabled.
+ ///
+ ///
+ /// The following code demonstrates how to use the FontAutoScalingEnabled property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool FontAutoScalingEnabled
+ {
+ get { return (bool)GetValue(FontAutoScalingEnabledProperty); }
+ set { SetValue(FontAutoScalingEnabledProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text color for the label.
+ ///
+ /// Used for helper, hint, and counter labels. For error label color, use the Stroke property in error state.
+ ///
+ ///
+ ///
+ /// The following code demonstrates how to use the TextColor property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ Microsoft.Maui.Font ITextElement.Font => (Microsoft.Maui.Font)GetValue(FontElement.FontProperty);
+
+ #endregion
+
+ #region Methods
+
+ double ITextElement.FontSizeDefaultValueCreator()
+ {
+ return 12d;
+ }
+
+ void ITextElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue)
+ {
+
+ }
+
+ void ITextElement.OnFontFamilyChanged(string oldValue, string newValue)
+ {
+
+ }
+
+ void ITextElement.OnFontSizeChanged(double oldValue, double newValue)
+ {
+
+ }
+
+ void ITextElement.OnFontChanged(Microsoft.Maui.Font oldValue, Microsoft.Maui.Font newValue)
+ {
+
+ }
+
+ ///
+ /// Called when the value changes.
+ ///
+ /// The old value of .
+ /// The new value of .
+ void ITextElement.OnFontAutoScalingEnabledChanged(bool oldValue, bool newValue)
+ {
+
+ }
+
+ #endregion
+ }
}
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs b/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
index 8777262d..4e1b7b94 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
@@ -1,10 +1,13 @@
-using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.EntryRenderer;
+using Syncfusion.Maui.Toolkit.EntryView;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
using Syncfusion.Maui.Toolkit.Themes;
+using Path = Microsoft.Maui.Controls.Shapes.Path;
namespace Syncfusion.Maui.Toolkit.TextInputLayout
{
public partial class SfTextInputLayout
- {
+ {
#region Public Methods
@@ -17,12 +20,12 @@ public partial class SfTextInputLayout
///
///
public new void Focus()
- {
- if (Content is InputView inputView)
- {
- inputView.Focus();
- }
- }
+ {
+ if (Content is InputView inputView)
+ {
+ inputView.Focus();
+ }
+ }
///
/// Removes focus from the input control within the .
@@ -33,1767 +36,2037 @@ public partial class SfTextInputLayout
///
///
public new void Unfocus()
- {
- if (Content is InputView inputView)
- {
- inputView.Unfocus();
- }
- }
- #endregion
-
- #region Internal Methods
- ///
- /// Invokes event.
- ///
- /// String.Empty event args.
- internal void InvokePasswordVisibilityToggledEvent(PasswordVisibilityToggledEventArgs args)
- {
- args.IsPasswordVisible = _isPasswordTextVisible;
- PasswordVisibilityToggled?.Invoke(this, args);
- }
-
- ///
- /// To get desired left padding.
- ///
- /// double value
- internal double GetLeftPadding()
- {
- return InputViewPadding.Left < 0 ? LeftPadding : InputViewPadding.Left;
- }
-
- ///
- /// To get desired top padding.
- ///
- /// double value
- internal double GetTopPadding()
- {
- double topPadding = TopPadding;
-
- // In widows entry has default padding value 6 so we reduce the value.
- if (DeviceInfo.Platform == DevicePlatform.WinUI && !(Content is Editor))
- {
- topPadding -= 6;
- }
- return InputViewPadding.Top < 0 ? topPadding : InputViewPadding.Top + GetDefaultTopPadding();
- }
-
- ///
- /// To get desired right padding.
- ///
- /// double value
- internal double GetRightPadding()
- {
- if (BaseLineMaxHeight <= 2)
- {
- return InputViewPadding.Right < 0 ? RightPadding : InputViewPadding.Right;
- }
- return InputViewPadding.Right < 0 ? RightPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2) : InputViewPadding.Right;
- }
-
- ///
- /// To get desired bottom padding.
- ///
- /// double value
- internal double GetBottomPadding()
- {
- double bottomPadding = BottomPadding;
-
- // In widows entry has default padding value 8 so we reduce the value.
- if (DeviceInfo.Platform == DevicePlatform.WinUI && !(Content is Editor))
- {
- bottomPadding -= 8;
- }
- return InputViewPadding.Bottom < 0 ? bottomPadding : InputViewPadding.Bottom + GetDefaultBottomPadding();
- }
-
- ///
- /// This method raised when input view OnHandlerChanged event has been occur.
- ///
- /// Entry.
- /// EventArgs.
- internal void OnTextInputViewHandlerChanged(object? sender, EventArgs e)
- {
- SetupAndroidView(sender);
- SetupWindowsView(sender);
- SetupIOSView(sender);
- }
-
- ///
- /// This method raised when input view of this control has been focused.
- ///
- /// Input View.
- /// FocusEventArgs.
- internal void OnTextInputViewFocused(object? sender, FocusEventArgs e)
- {
- IsLayoutFocused = true;
- }
-
- ///
- /// This method raised when input view of this control has been unfocused.
- ///
- /// Input Layout Base.
- /// Focus Event args.
- internal void OnTextInputViewUnfocused(object? sender, FocusEventArgs e)
- {
- IsLayoutFocused = false;
- }
-
- ///
- /// This method raised when input view text has been changed.
- ///
- /// InputView.
- /// TextChangedEventArgs.
- internal void OnTextInputViewTextChanged(object? sender, TextChangedEventArgs e)
- {
- if (sender is InputView)
- {
- Text = e.NewTextValue;
-
- if (string.IsNullOrEmpty(Text) && !IsLayoutFocused)
- {
- if (!IsHintAlwaysFloated)
- {
- IsHintFloated = false;
- IsHintDownToUp = true;
- InvalidateDrawable();
- }
- }
- else if (!string.IsNullOrEmpty(Text) && !IsHintFloated)
- {
- IsHintFloated = true;
- IsHintDownToUp = false;
- InvalidateDrawable();
- }
-
- // Clear icon can't draw when isClearIconVisible property updated based on text.
- // So here call the InvalidateDrawable to draw the clear icon.
- if (Text?.Length <= 1)
- {
- InvalidateDrawable();
- }
-
- //Call this method after bouncing issue has been fixed and implement the ShowCharCount API.
- //UpdateCounterLabelText();
- }
-
- // In Windows platform editors auto size is not working so here we manually call the measure method.
- if (DeviceInfo.Platform == DevicePlatform.WinUI && Content is Editor)
- {
- InvalidateMeasure();
- }
-
- }
-
- internal void StartAnimation()
- {
- if (IsHintFloated && IsLayoutFocused)
- {
- InvalidateDrawable();
- return;
- }
-
- if (string.IsNullOrEmpty(this.Text) && !IsLayoutFocused && !EnableFloating)
- {
- IsHintFloated = false;
- IsHintDownToUp = true;
- InvalidateDrawable();
- return;
- }
-
- if (!string.IsNullOrEmpty(Text) || IsHintAlwaysFloated || !EnableHintAnimation)
- {
- IsHintFloated = true;
- IsHintDownToUp = false;
- if (!EnableHintAnimation && !IsLayoutFocused && string.IsNullOrEmpty(Text))
- {
- IsHintFloated = false;
- IsHintDownToUp = true;
- }
- InvalidateDrawable();
- return;
- }
-
- _animatingFontSize = IsHintFloated ? FloatedHintFontSize : HintFontSize;
- UpdateStartAndEndValue();
- UpdateSizeStartAndEndValue();
- IsHintFloated = !IsHintFloated;
-
- // Text is disapper when the rect size is not compatible with text size, so here calculate the rect again.
- if (!IsHintFloated)
- {
- UpdateHintPosition();
- }
-
- _fontsize = (float)_fontSizeStart;
- var scalingAnimation = new Animation(OnScalingAnimationUpdate, _fontSizeStart, _fontSizeEnd, Easing.Linear);
-
- var translateAnimation = new Animation(OnTranslateAnimationUpdate, _translateStart, _translateEnd, Easing.SinIn);
-
- translateAnimation.WithConcurrent(scalingAnimation, 0, 1);
-
- translateAnimation.Commit(this, "showAnimator", rate: 7, length: (uint)DefaultAnimationDuration, finished: OnTranslateAnimationEnded, repeat: () => false);
-
- }
-
- #endregion
-
- #region Private Methods
- double GetDefaultTopPadding()
- {
- return (ShowHint ? (IsFilled ? DefaultAssistiveTextHeight : DefaultAssistiveTextHeight / 2) : BaseLineMaxHeight);
- }
-
- double GetDefaultBottomPadding()
- {
- return (ReserveSpaceForAssistiveLabels ? DefaultAssistiveLabelPadding + TotalAssistiveTextHeight() : 0);
- }
-
- double TotalAssistiveTextHeight()
- {
- return (ErrorTextSize.Height > HelperTextSize.Height ? ErrorTextSize.Height : HelperTextSize.Height);
- }
-
- void UpdateContentMargin(View view)
- {
- if (view == null)
- {
- return;
- }
-
- view.Margin = new Thickness()
- {
- Top = GetTopPadding(),
- Bottom = GetBottomPadding(),
- Left = GetLeftPadding(),
- Right = GetRightPadding()
- };
- }
-
- void OnTextInputLayoutUnloaded(object? sender, EventArgs e)
- {
- UnwireEvents();
- }
-
- void OnTextInputLayoutLoaded(object? sender, EventArgs e)
- {
- WireEvents();
- }
-
- void WireLabelStyleEvents()
- {
- if (HelperLabelStyle != null)
- {
- HelperLabelStyle.PropertyChanged += OnHelperLabelStylePropertyChanged;
- }
- if (ErrorLabelStyle != null)
- {
- ErrorLabelStyle.PropertyChanged += OnErrorLabelStylePropertyChanged;
- }
- if (HintLabelStyle != null)
- {
- HintLabelStyle.PropertyChanged += OnHintLabelStylePropertyChanged;
- }
- if (CounterLabelStyle != null)
- {
- CounterLabelStyle.PropertyChanged += OnCounterLabelStylePropertyChanged;
- }
- }
-
- void UnWireLabelStyleEvents()
- {
- if (HelperLabelStyle != null)
- {
- HelperLabelStyle.PropertyChanged -= OnHelperLabelStylePropertyChanged;
- }
- if (ErrorLabelStyle != null)
- {
- ErrorLabelStyle.PropertyChanged -= OnErrorLabelStylePropertyChanged;
- }
- if (HintLabelStyle != null)
- {
- HintLabelStyle.PropertyChanged -= OnHintLabelStylePropertyChanged;
- }
- if (CounterLabelStyle != null)
- {
- CounterLabelStyle.PropertyChanged -= OnCounterLabelStylePropertyChanged;
- }
- }
-
- void OnHintLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
- {
- HintFontSize = (float)(labelStyle.FontSize < 12d ? FloatedHintFontSize : labelStyle.FontSize);
- MatchLabelStyleProperty(_internalHintLabelStyle, labelStyle, e.PropertyName);
- InvalidateDrawable();
- }
- }
-
- void OnHelperLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
- {
- MatchLabelStyleProperty(_internalHelperLabelStyle, labelStyle, e.PropertyName);
- InvalidateDrawable();
- }
- }
-
- void OnErrorLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
- {
- MatchLabelStyleProperty(_internalErrorLabelStyle, labelStyle, e.PropertyName);
- InvalidateDrawable();
- }
- }
-
- void OnCounterLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
- {
- MatchLabelStyleProperty(_internalCounterLabelStyle, labelStyle, e.PropertyName);
- InvalidateDrawable();
- }
- }
-
- void MatchLabelStyleProperty(LabelStyle internalLabelStyle, LabelStyle labelStyle, string propertyName)
- {
- if (propertyName == nameof(LabelStyle.FontAttributes))
- internalLabelStyle.FontAttributes = labelStyle.FontAttributes;
- if (propertyName == nameof(LabelStyle.TextColor))
- internalLabelStyle.TextColor = labelStyle.TextColor;
- if (propertyName == nameof(LabelStyle.FontSize))
- internalLabelStyle.FontSize = labelStyle.FontSize;
- if (propertyName == nameof(LabelStyle.FontFamily))
- internalLabelStyle.FontFamily = labelStyle.FontFamily;
- }
-
- ///
- /// Raised when the property was changed.
- ///
- /// Boolean
- void OnEnabledPropertyChanged(bool isEnabled)
- {
- base.IsEnabled = isEnabled;
-
- if (Content != null)
- {
- Content.IsEnabled = isEnabled;
- }
- InvalidateDrawable();
- if (!IsEnabled)
- {
- if (ContainerType == ContainerType.Filled)
- {
- VisualStateManager.GoToState(this, "Disabled");
- }
- else if (ContainerType == ContainerType.Outlined)
- {
- VisualStateManager.GoToState(this, "OutlinedDisabled");
- }
- else if (ContainerType == ContainerType.None)
- {
- VisualStateManager.GoToState(this, "LineDisabled");
- }
- }
- }
-
- ///
- /// This method update the InputView, TrailView and LeadView position.
- ///
- void UpdateViewBounds()
- {
- UpdateLeadingViewPosition();
- UpdateTrailingViewPosition();
- UpdateContentPosition();
- InvalidateDrawable();
- }
-
- ///
- /// Gets the total number of lines required for given helper and error text.
- ///
- ///
- /// Number of Lines
- int GetHintLineCount(double totalWidth)
- {
- int number = 1;
-
- var availableWidth = GetHintTextWidth();
-
- if (availableWidth == 1 || availableWidth >= totalWidth)
- {
- return number;
- }
-
- number += (int)(totalWidth / availableWidth);
-
- return number;
- }
-
- ///
- /// Gets the totol number of lines required for given helper and error text.
- ///
- ///
- /// Number of Lines
- int GetAssistiveTextLineCount(double totalWidth)
- {
- int number = 1;
-
- var availableWidth = GetAssistiveTextWidth();
-
- if (availableWidth == 1)
- {
- return number;
- }
-
- number += (int)(totalWidth / availableWidth);
-
- return number;
- }
-
- ///
- /// This method return size based on ReserveSpaceForAssistiveLabels boolean property.
- ///
- ///
- /// Size
- SizeF GetLabelSize(SizeF size)
- {
- if (ReserveSpaceForAssistiveLabels)
- {
- return size;
- }
- else
- {
- size.Height = 0;
- size.Width = 0;
- return size;
- }
- }
-
- void SetupAndroidView(object? sender)
- {
+ {
+ if (Content is InputView inputView)
+ {
+ inputView.Unfocus();
+ }
+ }
+ #endregion
+
+ #region Internal Methods
+ ///
+ /// Invokes event.
+ ///
+ /// String.Empty event args.
+ internal void InvokePasswordVisibilityToggledEvent(PasswordVisibilityToggledEventArgs args)
+ {
+ args.IsPasswordVisible = _isPasswordTextVisible;
+ PasswordVisibilityToggled?.Invoke(this, args);
+ }
+
+ ///
+ /// To get desired left padding.
+ ///
+ /// double value
+ internal double GetLeftPadding()
+ {
+ return InputViewPadding.Left < 0 ? LeftPadding : InputViewPadding.Left;
+ }
+
+ ///
+ /// To get desired top padding.
+ ///
+ /// double value
+ internal double GetTopPadding()
+ {
+ double topPadding = TopPadding;
+
+ // In widows entry has default padding value 6 so we reduce the value.
+ if (DeviceInfo.Platform == DevicePlatform.WinUI && Content is not Editor)
+ {
+ topPadding -= 6;
+ }
+ return InputViewPadding.Top < 0 ? topPadding : InputViewPadding.Top + GetDefaultTopPadding();
+ }
+
+ ///
+ /// To get desired right padding.
+ ///
+ /// double value
+ internal double GetRightPadding()
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ return InputViewPadding.Right < 0 ? RightPadding : InputViewPadding.Right;
+ }
+ return InputViewPadding.Right < 0 ? RightPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2) : InputViewPadding.Right;
+ }
+
+ ///
+ /// To get desired bottom padding.
+ ///
+ /// double value
+ internal double GetBottomPadding()
+ {
+ double bottomPadding = BottomPadding;
+
+ // In widows entry has default padding value 8 so we reduce the value.
+ if (DeviceInfo.Platform == DevicePlatform.WinUI && Content is not Editor)
+ {
+ bottomPadding -= 8;
+ }
+ return InputViewPadding.Bottom < 0 ? bottomPadding : InputViewPadding.Bottom + GetDefaultBottomPadding();
+ }
+
+ ///
+ /// This method raised when input view OnHandlerChanged event has been occur.
+ ///
+ /// Entry.
+ /// EventArgs.
+ internal void OnTextInputViewHandlerChanged(object? sender, EventArgs e)
+ {
+ SetupAndroidView(sender);
+ SetupWindowsView(sender);
+ SetupIOSView(sender);
+ }
+
+ ///
+ /// This method raised when input view of this control has been focused.
+ ///
+ /// Input View.
+ /// FocusEventArgs.
+ internal void OnTextInputViewFocused(object? sender, FocusEventArgs e)
+ {
+ IsLayoutFocused = true;
+ }
+
+ ///
+ /// This method raised when input view of this control has been unfocused.
+ ///
+ /// Input Layout Base.
+ /// Focus Event args.
+ internal void OnTextInputViewUnfocused(object? sender, FocusEventArgs e)
+ {
+ IsLayoutFocused = false;
+ }
+
+ ///
+ /// This method raised when input view text has been changed.
+ ///
+ /// InputView.
+ /// TextChangedEventArgs.
+ internal void OnTextInputViewTextChanged(object? sender, TextChangedEventArgs e)
+ {
+ if (sender is InputView)
+ {
+ Text = e.NewTextValue;
+
+ if (string.IsNullOrEmpty(Text) && !IsLayoutFocused)
+ {
+ if (!IsHintAlwaysFloated)
+ {
+ IsHintFloated = false;
+ IsHintDownToUp = true;
+ InvalidateDrawable();
+ }
+ }
+ else if (!string.IsNullOrEmpty(Text) && !IsHintFloated)
+ {
+ IsHintFloated = true;
+ IsHintDownToUp = false;
+ InvalidateDrawable();
+ }
+
+ // Clear icon can't draw when isClearIconVisible property updated based on text.
+ // So here call the InvalidateDrawable to draw the clear icon.
+ if (Text?.Length <= 1)
+ {
+ InvalidateDrawable();
+ }
+
+ //Call this method after bouncing issue has been fixed and implement the ShowCharCount API.
+ //UpdateCounterLabelText();
+ }
+
+ // In Windows platform editors auto size is not working so here we manually call the measure method.
+ if (DeviceInfo.Platform == DevicePlatform.WinUI && Content is Editor)
+ {
+ InvalidateMeasure();
+ }
+
+ }
+
+ internal void StartAnimation()
+ {
+ if (IsHintFloated && IsLayoutFocused)
+ {
+ InvalidateDrawable();
+ return;
+ }
+
+ if (string.IsNullOrEmpty(Text) && !IsLayoutFocused && !EnableFloating)
+ {
+ IsHintFloated = false;
+ IsHintDownToUp = true;
+ InvalidateDrawable();
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(Text) || IsHintAlwaysFloated || !EnableHintAnimation)
+ {
+ IsHintFloated = true;
+ IsHintDownToUp = false;
+ if (!EnableHintAnimation && !IsLayoutFocused && string.IsNullOrEmpty(Text))
+ {
+ IsHintFloated = false;
+ IsHintDownToUp = true;
+ }
+ InvalidateDrawable();
+ return;
+ }
+
+ _animatingFontSize = IsHintFloated ? FloatedHintFontSize : HintFontSize;
+ UpdateStartAndEndValue();
+ UpdateSizeStartAndEndValue();
+ IsHintFloated = !IsHintFloated;
+
+ // Text is disappear when the rect size is not compatible with text size, so here calculate the rect again.
+ if (!IsHintFloated)
+ {
+ UpdateHintPosition();
+ }
+ _fontsize = (float)_fontSizeStart;
+ var scalingAnimation = new Animation(OnScalingAnimationUpdate, _fontSizeStart, _fontSizeEnd, Easing.Linear);
+ var translateAnimation = new Animation(OnTranslateAnimationUpdate, _translateStart, _translateEnd, Easing.SinIn);
+ // Add scaling animation concurrently
+ translateAnimation.WithConcurrent(scalingAnimation, 0, 1);
+ translateAnimation.Commit(this, "showAnimator", rate: 7, length: (uint)DefaultAnimationDuration, finished: OnTranslateAnimationEnded, repeat: () => false);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ void OnTranslateAnimationUpdate(double value)
+ {
+ _isAnimating = true;
+ _hintRect.Y = (float)value;
+ _animatingFontSize = _fontsize;
+ InvalidateDrawable();
+ }
+
+ double GetDefaultTopPadding()
+ {
+ return (ShowHint ? (IsFilled ? DefaultAssistiveTextHeight : DefaultAssistiveTextHeight / 2) : BaseLineMaxHeight);
+ }
+
+ double GetDefaultBottomPadding()
+ {
+ return (ReserveSpaceForAssistiveLabels ? DefaultAssistiveLabelPadding + TotalAssistiveTextHeight() : 0);
+ }
+
+ double TotalAssistiveTextHeight()
+ {
+ return (ErrorTextSize.Height > HelperTextSize.Height ? ErrorTextSize.Height : HelperTextSize.Height);
+ }
+
+ void UpdateContentMargin(View view)
+ {
+ if (view == null)
+ {
+ return;
+ }
+
+ view.Margin = new Thickness()
+ {
+ Top = GetTopPadding(),
+ Bottom = GetBottomPadding(),
+ Left = GetLeftPadding(),
+ Right = GetRightPadding()
+ };
+ }
+
+ void OnTextInputLayoutUnloaded(object? sender, EventArgs e)
+ {
+ UnwireEvents();
+ }
+
+ void OnTextInputLayoutLoaded(object? sender, EventArgs e)
+ {
+ WireEvents();
+ }
+
+ void WireLabelStyleEvents()
+ {
+ if (HelperLabelStyle != null)
+ {
+ HelperLabelStyle.PropertyChanged += OnHelperLabelStylePropertyChanged;
+ }
+ if (ErrorLabelStyle != null)
+ {
+ ErrorLabelStyle.PropertyChanged += OnErrorLabelStylePropertyChanged;
+ }
+ if (HintLabelStyle != null)
+ {
+ HintLabelStyle.PropertyChanged += OnHintLabelStylePropertyChanged;
+ }
+ if (CounterLabelStyle != null)
+ {
+ CounterLabelStyle.PropertyChanged += OnCounterLabelStylePropertyChanged;
+ }
+ }
+
+ void UnWireLabelStyleEvents()
+ {
+ if (HelperLabelStyle != null)
+ {
+ HelperLabelStyle.PropertyChanged -= OnHelperLabelStylePropertyChanged;
+ }
+ if (ErrorLabelStyle != null)
+ {
+ ErrorLabelStyle.PropertyChanged -= OnErrorLabelStylePropertyChanged;
+ }
+ if (HintLabelStyle != null)
+ {
+ HintLabelStyle.PropertyChanged -= OnHintLabelStylePropertyChanged;
+ }
+ if (CounterLabelStyle != null)
+ {
+ CounterLabelStyle.PropertyChanged -= OnCounterLabelStylePropertyChanged;
+ }
+ }
+
+ void OnHintLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
+ {
+ HintFontSize = (float)(labelStyle.FontSize < 12d ? FloatedHintFontSize : labelStyle.FontSize);
+ MatchLabelStyleProperty(_internalHintLabelStyle, labelStyle, e.PropertyName);
+ InvalidateDrawable();
+ }
+ }
+
+ void OnHelperLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
+ {
+ MatchLabelStyleProperty(_internalHelperLabelStyle, labelStyle, e.PropertyName);
+ InvalidateDrawable();
+ }
+ }
+
+ void OnErrorLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
+ {
+ MatchLabelStyleProperty(_internalErrorLabelStyle, labelStyle, e.PropertyName);
+ InvalidateDrawable();
+ }
+ }
+
+ void OnCounterLabelStylePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (sender is LabelStyle labelStyle && !string.IsNullOrEmpty(e.PropertyName))
+ {
+ MatchLabelStyleProperty(_internalCounterLabelStyle, labelStyle, e.PropertyName);
+ InvalidateDrawable();
+ }
+ }
+
+ void MatchLabelStyleProperty(LabelStyle internalLabelStyle, LabelStyle labelStyle, string propertyName)
+ {
+ if (propertyName == nameof(LabelStyle.FontAttributes))
+ {
+ internalLabelStyle.FontAttributes = labelStyle.FontAttributes;
+ }
+
+ if (propertyName == nameof(LabelStyle.TextColor))
+ {
+ internalLabelStyle.TextColor = labelStyle.TextColor;
+ }
+
+ if (propertyName == nameof(LabelStyle.FontSize))
+ {
+ internalLabelStyle.FontSize = labelStyle.FontSize;
+ }
+
+ if (propertyName == nameof(LabelStyle.FontFamily))
+ {
+ internalLabelStyle.FontFamily = labelStyle.FontFamily;
+ }
+ }
+
+ ///
+ /// Raised when the property was changed.
+ ///
+ /// Boolean
+ void OnEnabledPropertyChanged(bool isEnabled)
+ {
+ base.IsEnabled = isEnabled;
+
+ if (Content != null)
+ {
+ Content.IsEnabled = isEnabled;
+ }
+ InvalidateDrawable();
+ if (!IsEnabled)
+ {
+ if (ContainerType == ContainerType.Filled)
+ {
+ VisualStateManager.GoToState(this, "Disabled");
+ }
+ else if (ContainerType == ContainerType.Outlined)
+ {
+ VisualStateManager.GoToState(this, "OutlinedDisabled");
+ }
+ else if (ContainerType == ContainerType.None)
+ {
+ VisualStateManager.GoToState(this, "LineDisabled");
+ }
+ }
+ }
+
+ ///
+ /// This method update the InputView, TrailView and LeadView position.
+ ///
+ void UpdateViewBounds()
+ {
+ UpdateLeadingViewPosition();
+ UpdateTrailingViewPosition();
+ UpdateContentPosition();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Gets the total number of lines required for given helper and error text.
+ ///
+ ///
+ /// Number of Lines
+ int GetHintLineCount(double totalWidth)
+ {
+ int number = 1;
+
+ var availableWidth = GetHintTextWidth();
+
+ if (availableWidth == 1 || availableWidth >= totalWidth)
+ {
+ return number;
+ }
+
+ number += (int)(totalWidth / availableWidth);
+
+ return number;
+ }
+
+ ///
+ /// Gets the totol number of lines required for given helper and error text.
+ ///
+ ///
+ /// Number of Lines
+ int GetAssistiveTextLineCount(double totalWidth)
+ {
+ int number = 1;
+
+ var availableWidth = GetAssistiveTextWidth();
+
+ if (availableWidth == 1)
+ {
+ return number;
+ }
+
+ number += (int)(totalWidth / availableWidth);
+
+ return number;
+ }
+
+ ///
+ /// This method return size based on ReserveSpaceForAssistiveLabels boolean property.
+ ///
+ ///
+ /// Size
+ SizeF GetLabelSize(SizeF size)
+ {
+ if (ReserveSpaceForAssistiveLabels)
+ {
+ return size;
+ }
+ else
+ {
+ size.Height = 0;
+ size.Width = 0;
+ return size;
+ }
+ }
+
+ void SetupAndroidView(object? sender)
+ {
#if ANDROID
- if (sender is View view && view.Handler != null && view.Handler.PlatformView is Android.Views.View androidView)
- {
- androidView.SetBackgroundColor(Android.Graphics.Color.Transparent);
- androidView.SetPadding(0, 0, 0, 0);
- }
+ if (sender is View view && view.Handler != null && view.Handler.PlatformView is Android.Views.View androidView)
+ {
+ androidView.SetBackgroundColor(Android.Graphics.Color.Transparent);
+ androidView.SetPadding(0, 0, 0, 0);
+ }
#endif
- }
+ }
- void SetupWindowsView(object? sender)
- {
+ void SetupWindowsView(object? sender)
+ {
#if WINDOWS
- if (sender is InputView inputView && inputView.Handler != null && inputView.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox windowEntry)
- {
- ConfigureWindowsEntry(windowEntry);
- }
- else if (sender is Picker picker && picker.Handler != null && picker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
- {
- ConfigureWindowsPicker(windowPicker);
- }
- else if (sender is DatePicker datePicker && datePicker.Handler != null && datePicker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.CalendarDatePicker windowDatePicker)
- {
- ConfigureWindowsDatePicker(windowDatePicker);
- }
- else if (sender is TimePicker timePicker && timePicker.Handler != null && timePicker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TimePicker windowTimePicker)
- {
- ConfigureWindowsTimePicker(windowTimePicker);
- }
+ if (sender is InputView inputView && inputView.Handler != null && inputView.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TextBox windowEntry)
+ {
+ ConfigureWindowsEntry(windowEntry);
+ }
+ else if (sender is Picker picker && picker.Handler != null && picker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
+ {
+ ConfigureWindowsPicker(windowPicker);
+ }
+ else if (sender is DatePicker datePicker && datePicker.Handler != null && datePicker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.CalendarDatePicker windowDatePicker)
+ {
+ ConfigureWindowsDatePicker(windowDatePicker);
+ }
+ else if (sender is TimePicker timePicker && timePicker.Handler != null && timePicker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.TimePicker windowTimePicker)
+ {
+ ConfigureWindowsTimePicker(windowTimePicker);
+ }
#endif
- }
+ }
- void SetupIOSView(object? sender)
- {
+ void SetupIOSView(object? sender)
+ {
#if IOS || MACCATALYST
- if (sender is View view && view.Handler != null && view.Handler.PlatformView is UIKit.UITextField iOSEntry)
- {
- ConfigureIOSTextField(iOSEntry);
- }
-
- if (sender is Editor editor && editor.Handler != null && editor.Handler.PlatformView is UIKit.UITextView iOSEditor)
- {
- ConfigureIOSTextView(iOSEditor);
- }
+ if (sender is View view && view.Handler != null && view.Handler.PlatformView is UIKit.UITextField iOSEntry)
+ {
+ ConfigureIOSTextField(iOSEntry);
+ }
+
+ if (sender is Editor editor && editor.Handler != null && editor.Handler.PlatformView is UIKit.UITextView iOSEditor)
+ {
+ ConfigureIOSTextView(iOSEditor);
+ }
#endif
- }
+ }
#if WINDOWS
- void ConfigureWindowsEntry(Microsoft.UI.Xaml.Controls.TextBox windowEntry)
- {
- windowEntry.LosingFocus -= OnWindowsEntryLosingFocus;
- windowEntry.LosingFocus += OnWindowsEntryLosingFocus;
- windowEntry.PointerEntered -= OnPointerEntered;
- windowEntry.PointerEntered += OnPointerEntered;
- windowEntry.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
- windowEntry.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
- windowEntry.Padding = new Microsoft.UI.Xaml.Thickness(0);
- windowEntry.Resources["TextControlBorderThemeThicknessFocused"] = windowEntry.BorderThickness;
- }
-
- void ConfigureWindowsPicker(Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
- {
- windowPicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
- windowPicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
- windowPicker.Padding = new Microsoft.UI.Xaml.Thickness(0);
- windowPicker.Resources["TextControlBorderThemeThicknessFocused"] = windowPicker.BorderThickness;
- windowPicker.Resources["ComboBoxDropDownGlyphForeground"] = windowPicker.Background;
- windowPicker.Header = null;
- windowPicker.HeaderTemplate = null;
- }
-
- void ConfigureWindowsDatePicker(Microsoft.UI.Xaml.Controls.CalendarDatePicker windowDatePicker)
- {
- windowDatePicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
- windowDatePicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
- //In native windows date picker text block has default padding value of (12, 0,0,2).
- windowDatePicker.Margin = new Microsoft.UI.Xaml.Thickness(-12, 0, 0, -2);
- windowDatePicker.HorizontalContentAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Center;
- windowDatePicker.Resources["TextControlBorderThemeThicknessFocused"] = windowDatePicker.BorderThickness;
- windowDatePicker.Resources["SystemColorHighlightColorBrush"] = windowDatePicker.Background;
- windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForeground"] = windowDatePicker.Background;
- windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForegroundPointerOver"] = windowDatePicker.Background;
- windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForegroundPressed"] = windowDatePicker.Background;
- }
-
- void ConfigureWindowsTimePicker(Microsoft.UI.Xaml.Controls.TimePicker windowTimePicker)
- {
- windowTimePicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
- windowTimePicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
- windowTimePicker.Padding = new Microsoft.UI.Xaml.Thickness(0);
- windowTimePicker.Resources["TextControlBorderThemeThicknessFocused"] = windowTimePicker.BorderThickness;
- windowTimePicker.Resources["TimePickerSpacerThemeWidth"] = "0";
- }
-
- void OnPointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
- {
- if (_effectsRenderer != null && _effectsRenderer.HighlightBounds.Width > 0 && _effectsRenderer.HighlightBounds.Height > 0)
- {
- _effectsRenderer.RemoveHighlight();
- }
- }
-
- void OnWindowsEntryLosingFocus(Microsoft.UI.Xaml.UIElement element, Microsoft.UI.Xaml.Input.LosingFocusEventArgs args)
- {
- if (IsIconPressed && element is Microsoft.UI.Xaml.Controls.TextBox textbox)
- {
- args.TryCancel();
- if (Content is Entry entry)
- {
- textbox.SelectionStart = entry.CursorPosition;
- }
- IsIconPressed = false;
- }
- }
+ void ConfigureWindowsEntry(Microsoft.UI.Xaml.Controls.TextBox windowEntry)
+ {
+ windowEntry.LosingFocus -= OnWindowsEntryLosingFocus;
+ windowEntry.LosingFocus += OnWindowsEntryLosingFocus;
+ windowEntry.PointerEntered -= OnPointerEntered;
+ windowEntry.PointerEntered += OnPointerEntered;
+ windowEntry.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
+ windowEntry.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
+ windowEntry.Padding = new Microsoft.UI.Xaml.Thickness(0);
+ windowEntry.Resources["TextControlBorderThemeThicknessFocused"] = windowEntry.BorderThickness;
+ }
+
+ void ConfigureWindowsPicker(Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
+ {
+ windowPicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
+ windowPicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
+ windowPicker.Padding = new Microsoft.UI.Xaml.Thickness(0);
+ windowPicker.Resources["TextControlBorderThemeThicknessFocused"] = windowPicker.BorderThickness;
+ windowPicker.Resources["ComboBoxDropDownGlyphForeground"] = windowPicker.Background;
+ windowPicker.Header = null;
+ windowPicker.HeaderTemplate = null;
+ }
+
+ void ConfigureWindowsDatePicker(Microsoft.UI.Xaml.Controls.CalendarDatePicker windowDatePicker)
+ {
+ windowDatePicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
+ windowDatePicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
+ //In native windows date picker text block has default padding value of (12, 0,0,2).
+ windowDatePicker.Margin = new Microsoft.UI.Xaml.Thickness(-12, 0, 0, -2);
+ windowDatePicker.HorizontalContentAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Center;
+ windowDatePicker.Resources["TextControlBorderThemeThicknessFocused"] = windowDatePicker.BorderThickness;
+ windowDatePicker.Resources["SystemColorHighlightColorBrush"] = windowDatePicker.Background;
+ windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForeground"] = windowDatePicker.Background;
+ windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForegroundPointerOver"] = windowDatePicker.Background;
+ windowDatePicker.Resources["CalendarDatePickerCalendarGlyphForegroundPressed"] = windowDatePicker.Background;
+ }
+
+ void ConfigureWindowsTimePicker(Microsoft.UI.Xaml.Controls.TimePicker windowTimePicker)
+ {
+ windowTimePicker.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
+ windowTimePicker.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
+ windowTimePicker.Padding = new Microsoft.UI.Xaml.Thickness(0);
+ windowTimePicker.Resources["TextControlBorderThemeThicknessFocused"] = windowTimePicker.BorderThickness;
+ windowTimePicker.Resources["TimePickerSpacerThemeWidth"] = "0";
+ }
+
+ void OnPointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
+ {
+ if (_effectsRenderer != null && _effectsRenderer.HighlightBounds.Width > 0 && _effectsRenderer.HighlightBounds.Height > 0)
+ {
+ _effectsRenderer.RemoveHighlight();
+ }
+ }
+
+ void OnWindowsEntryLosingFocus(Microsoft.UI.Xaml.UIElement element, Microsoft.UI.Xaml.Input.LosingFocusEventArgs args)
+ {
+ if (IsIconPressed && element is Microsoft.UI.Xaml.Controls.TextBox textbox)
+ {
+ args.TryCancel();
+ if (Content is Entry entry)
+ {
+ textbox.SelectionStart = entry.CursorPosition;
+ }
+ IsIconPressed = false;
+ }
+ }
#endif
#if IOS || MACCATALYST
- void ConfigureIOSTextField(UIKit.UITextField iOSEntry)
- {
- iOSEntry.BackgroundColor = UIKit.UIColor.Clear;
- iOSEntry.BorderStyle = UIKit.UITextBorderStyle.None;
- iOSEntry.Layer.BorderWidth = 0f;
- iOSEntry.Layer.BorderColor = UIKit.UIColor.Clear.CGColor;
- iOSEntry.LeftViewMode = UIKit.UITextFieldViewMode.Never;
- iOSEntry.ShouldEndEditing = (iOSEntry) =>
- {
- return !IsIconPressed;
- };
- }
-
- void ConfigureIOSTextView(UIKit.UITextView iOSEditor)
- {
- iOSEditor.BackgroundColor = UIKit.UIColor.Clear;
- iOSEditor.Layer.BorderWidth = 0f;
- iOSEditor.Layer.BorderColor = UIKit.UIColor.Clear.CGColor;
- iOSEditor.TextContainer.LineFragmentPadding = 0f;
- }
+ void ConfigureIOSTextField(UIKit.UITextField iOSEntry)
+ {
+ iOSEntry.BackgroundColor = UIKit.UIColor.Clear;
+ iOSEntry.BorderStyle = UIKit.UITextBorderStyle.None;
+ iOSEntry.Layer.BorderWidth = 0f;
+ iOSEntry.Layer.BorderColor = UIKit.UIColor.Clear.CGColor;
+ iOSEntry.LeftViewMode = UIKit.UITextFieldViewMode.Never;
+ iOSEntry.ShouldEndEditing += ShouldEndEditing;
+ uiEntry = iOSEntry;
+ }
+
+ ///
+ /// Determines whether editing should end for the given text field.
+ /// Returns false if an icon is pressed, otherwise returns true.
+ ///
+ /// The UITextField being edited.
+ /// A boolean indicating whether editing should end.
+ bool ShouldEndEditing(UIKit.UITextField textField)
+ {
+ return (this.IsIconPressed ? false : true);
+ }
+
+ void ConfigureIOSTextView(UIKit.UITextView iOSEditor)
+ {
+ iOSEditor.BackgroundColor = UIKit.UIColor.Clear;
+ iOSEditor.Layer.BorderWidth = 0f;
+ iOSEditor.Layer.BorderColor = UIKit.UIColor.Clear.CGColor;
+ iOSEditor.TextContainer.LineFragmentPadding = 0f;
+ }
#endif
- ///
- /// This method sets the default stroke value based on control states.
- ///
- void AddDefaultVSM()
- {
- VisualStateGroupList visualStateGroupList = new VisualStateGroupList() { };
-
- VisualStateGroup visualStateGroup = new VisualStateGroup();
-
- visualStateGroup.Name = "CommonStates";
-
- VisualState focusedState = new VisualState() { Name = "Focused" };
- Setter focusedStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultFocusStrokeColor };
- Setter focusedBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = ContainerBackground };
- focusedState.Setters.Add(focusedStrokeSetter);
- focusedState.Setters.Add(focusedBackgroundSetter);
-
- VisualState errorState = new VisualState() { Name = "Error" };
- Setter errorStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultErrorStrokeColor };
- errorState.Setters.Add(errorStrokeSetter);
-
- VisualState normalState = new VisualState() { Name = "Normal" };
- Setter normalStrokeSetter = new Setter() { Property = StrokeProperty, Value = Stroke };
- Setter normalBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = ContainerBackground };
- normalState.Setters.Add(normalStrokeSetter);
- normalState.Setters.Add(normalBackgroundSetter);
-
- VisualState disabledState = new VisualState() { Name = "Disabled" };
- Setter disabledStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultDisabledStrokeColor };
- Setter disabledBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = _defaultDisabledContainerBackground };
- disabledState.Setters.Add(disabledStrokeSetter);
- disabledState.Setters.Add(disabledBackgroundSetter);
-
-
- visualStateGroup.States.Add(normalState);
- visualStateGroup.States.Add(focusedState);
- visualStateGroup.States.Add(errorState);
- visualStateGroup.States.Add(disabledState);
- visualStateGroupList.Add(visualStateGroup);
- VisualStateManager.SetVisualStateGroups(this, visualStateGroupList);
- }
-
- ///
- /// This method unhook the all the hooked event of the entry.
- ///
- void UnwireEvents()
- {
- if (Content != null)
- {
- switch (Content)
- {
- case InputView inputView:
- inputView.Focused -= OnTextInputViewFocused;
- inputView.Unfocused -= OnTextInputViewUnfocused;
- inputView.TextChanged -= OnTextInputViewTextChanged;
- inputView.HandlerChanged -= OnTextInputViewHandlerChanged;
- break;
- case Picker picker:
- picker.Focused -= OnTextInputViewFocused;
- picker.Unfocused -= OnTextInputViewUnfocused;
- picker.HandlerChanged -= OnTextInputViewHandlerChanged;
- picker.SelectedIndexChanged -= OnPickerSelectedIndexChanged;
- break;
- case TimePicker timePicker:
- timePicker.HandlerChanged -= OnTextInputViewHandlerChanged;
- timePicker.Focused -= OnTextInputViewFocused;
- timePicker.Unfocused -= OnTextInputViewUnfocused;
- break;
- case DatePicker datePicker:
- datePicker.HandlerChanged -= OnTextInputViewHandlerChanged;
- datePicker.Focused -= OnTextInputViewFocused;
- datePicker.Unfocused -= OnTextInputViewUnfocused;
- break;
- }
+ ///
+ /// This method sets the default stroke value based on control states.
+ ///
+ void AddDefaultVSM()
+ {
+ VisualStateGroupList visualStateGroupList = [];
+
+ VisualStateGroup visualStateGroup = new VisualStateGroup
+ {
+ Name = "CommonStates"
+ };
+
+ VisualState focusedState = new VisualState() { Name = "Focused" };
+ Setter focusedStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultFocusStrokeColor };
+ Setter focusedBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = ContainerBackground };
+ focusedState.Setters.Add(focusedStrokeSetter);
+ focusedState.Setters.Add(focusedBackgroundSetter);
+
+ VisualState errorState = new VisualState() { Name = "Error" };
+ Setter errorStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultErrorStrokeColor };
+ errorState.Setters.Add(errorStrokeSetter);
+
+ VisualState normalState = new VisualState() { Name = "Normal" };
+ Setter normalStrokeSetter = new Setter() { Property = StrokeProperty, Value = Stroke };
+ Setter normalBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = ContainerBackground };
+ normalState.Setters.Add(normalStrokeSetter);
+ normalState.Setters.Add(normalBackgroundSetter);
+
+ VisualState disabledState = new VisualState() { Name = "Disabled" };
+ Setter disabledStrokeSetter = new Setter() { Property = StrokeProperty, Value = _defaultDisabledStrokeColor };
+ Setter disabledBackgroundSetter = new Setter() { Property = ContainerBackgroundProperty, Value = _defaultDisabledContainerBackground };
+ disabledState.Setters.Add(disabledStrokeSetter);
+ disabledState.Setters.Add(disabledBackgroundSetter);
+
+
+ visualStateGroup.States.Add(normalState);
+ visualStateGroup.States.Add(focusedState);
+ visualStateGroup.States.Add(errorState);
+ visualStateGroup.States.Add(disabledState);
+ visualStateGroupList.Add(visualStateGroup);
+ VisualStateManager.SetVisualStateGroups(this, visualStateGroupList);
+ }
+
+ ///
+ /// This method unhook the all the hooked event of the entry.
+ ///
+ void UnwireEvents()
+ {
+ if (Content != null)
+ {
+ switch (Content)
+ {
+ case InputView inputView:
+ inputView.Focused -= OnTextInputViewFocused;
+ inputView.Unfocused -= OnTextInputViewUnfocused;
+ inputView.TextChanged -= OnTextInputViewTextChanged;
+ inputView.HandlerChanged -= OnTextInputViewHandlerChanged;
+ break;
+ case Picker picker:
+ picker.Focused -= OnTextInputViewFocused;
+ picker.Unfocused -= OnTextInputViewUnfocused;
+ picker.HandlerChanged -= OnTextInputViewHandlerChanged;
+ picker.SelectedIndexChanged -= OnPickerSelectedIndexChanged;
+ break;
+ case TimePicker timePicker:
+ timePicker.HandlerChanged -= OnTextInputViewHandlerChanged;
+ timePicker.Focused -= OnTextInputViewFocused;
+ timePicker.Unfocused -= OnTextInputViewUnfocused;
+ break;
+ case DatePicker datePicker:
+ datePicker.HandlerChanged -= OnTextInputViewHandlerChanged;
+ datePicker.Focused -= OnTextInputViewFocused;
+ datePicker.Unfocused -= OnTextInputViewUnfocused;
+ break;
+ case Syncfusion.Maui.Toolkit.NumericEntry.SfNumericEntry numericEntryView:
+ if(numericEntryView.Children[0] is Entry numericInputView)
+ {
+ numericInputView.Focused -= OnTextInputViewFocused;
+ numericInputView.Unfocused -= OnTextInputViewUnfocused;
+ numericInputView.TextChanged -= OnTextInputViewTextChanged;
+ numericInputView.HandlerChanged -= OnTextInputViewHandlerChanged;
+ }
+ break;
+ }
#if MACCATALYST || IOS
- if (Content is View view && view.Handler != null && view.Handler.PlatformView is UIKit.UITextField iOSEntry)
+ if (Handler == null && uiEntry != null)
{
// Setting ShouldEndEditing to null to prevent memory leaks
- iOSEntry.ShouldEndEditing = null;
+ uiEntry.ShouldEndEditing -= ShouldEndEditing;
+ uiEntry = null;
}
#endif
}
+
+ UnWireLabelStyleEvents();
+ }
+
+ ///
+ /// Recursively call the increment and decrement value
+ ///
+ /// The touchpoint
+ ///
+ async Task RecursivePressedAsync(Point touchpoint)
+ {
+ if (!_isPressOccurring)
+ {
+ return;
+ }
+
+ if (this.Content is ITextInputLayout numericEntry)
+ {
+ if ((_downIconRectF.Contains(touchpoint) && IsUpDownVerticalAlignment) || (_upIconRectF.Contains(touchpoint) && !IsUpDownVerticalAlignment))
+ {
+ numericEntry.UpButtonPressed();
+ }
+ else if ((_upIconRectF.Contains(touchpoint) && IsUpDownVerticalAlignment) || (_downIconRectF.Contains(touchpoint) && !IsUpDownVerticalAlignment))
+ {
+ numericEntry.DownButtonPressed();
+ }
+ }
+ // Wait for the specified delay
+ await Task.Delay(200);
+
+ // Call the method recursively
+ await RecursivePressedAsync(touchpoint);
+ }
+
+ async void StartPressTimer(Point touchpoint)
+ {
+ _isPressOccurring = true;
+ await Task.Delay(200);
+ await RecursivePressedAsync(touchpoint);
+ }
+
+ void WireEvents()
+ {
+ if (Content != null)
+ {
+ switch (Content)
+ {
+ case InputView inputView:
+ inputView.Focused += OnTextInputViewFocused;
+ inputView.Unfocused += OnTextInputViewUnfocused;
+ inputView.TextChanged += OnTextInputViewTextChanged;
+ inputView.HandlerChanged += OnTextInputViewHandlerChanged;
+ break;
+ case Picker picker:
+ picker.Focused += OnTextInputViewFocused;
+ picker.Unfocused += OnTextInputViewUnfocused;
+ picker.HandlerChanged += OnTextInputViewHandlerChanged;
+ picker.SelectedIndexChanged += OnPickerSelectedIndexChanged;
+ break;
+ case TimePicker timePicker:
+ timePicker.HandlerChanged += OnTextInputViewHandlerChanged;
+ timePicker.Focused += OnTextInputViewFocused;
+ timePicker.Unfocused += OnTextInputViewUnfocused;
+ break;
+ case DatePicker datePicker:
+ datePicker.HandlerChanged += OnTextInputViewHandlerChanged;
+ datePicker.Focused += OnTextInputViewFocused;
+ datePicker.Unfocused += OnTextInputViewUnfocused;
+ break;
+ case Syncfusion.Maui.Toolkit.NumericEntry.SfNumericEntry numericEntryView:
+ if(numericEntryView.Children[0] is Entry numericInputView)
+ {
+ numericInputView.Focused += OnTextInputViewFocused;
+ numericInputView.Unfocused += OnTextInputViewUnfocused;
+ numericInputView.TextChanged += OnTextInputViewTextChanged;
+ numericInputView.HandlerChanged += OnTextInputViewHandlerChanged;
+ }
+ break;
+ }
+ }
+
+ WireLabelStyleEvents();
+ }
- UnWireLabelStyleEvents();
- }
-
- void WireEvents()
- {
- if (Content != null)
- {
- switch (Content)
- {
- case InputView inputView:
- inputView.Focused += OnTextInputViewFocused;
- inputView.Unfocused += OnTextInputViewUnfocused;
- inputView.TextChanged += OnTextInputViewTextChanged;
- inputView.HandlerChanged += OnTextInputViewHandlerChanged;
- break;
- case Picker picker:
- picker.Focused += OnTextInputViewFocused;
- picker.Unfocused += OnTextInputViewUnfocused;
- picker.HandlerChanged += OnTextInputViewHandlerChanged;
- picker.SelectedIndexChanged += OnPickerSelectedIndexChanged;
- break;
- case TimePicker timePicker:
- timePicker.HandlerChanged += OnTextInputViewHandlerChanged;
- timePicker.Focused += OnTextInputViewFocused;
- timePicker.Unfocused += OnTextInputViewUnfocused;
- break;
- case DatePicker datePicker:
- datePicker.HandlerChanged += OnTextInputViewHandlerChanged;
- datePicker.Focused += OnTextInputViewFocused;
- datePicker.Unfocused += OnTextInputViewUnfocused;
- break;
- }
- }
-
- WireLabelStyleEvents();
- }
-
- ///
- /// This method changed the password toggle visibility icon and drop down window visibility into collapesed icon and viceversa.
- ///
+ ///
+ /// This method changed the password toggle visibility icon and drop down window visibility into collapesed icon and viceversa.
+ ///
#if IOS || MACCATALYST
- async void ToggleIcon()
+ async void ToggleIcon()
#else
- void ToggleIcon()
+ void ToggleIcon()
#endif
- {
- if (Content is Entry entry)
- {
- _isPasswordTextVisible = !_isPasswordTextVisible;
- entry.IsPassword = !_isPasswordTextVisible;
- InvokePasswordVisibilityToggledEvent(new PasswordVisibilityToggledEventArgs());
- InvalidateDrawable();
- }
+ {
+ if (Content is Entry entry)
+ {
+ _isPasswordTextVisible = !_isPasswordTextVisible;
+ entry.IsPassword = !_isPasswordTextVisible;
+ InvokePasswordVisibilityToggledEvent(new PasswordVisibilityToggledEventArgs());
+ InvalidateDrawable();
+ }
#if IOS || MACCATALYST
- await Task.Delay(10);
- IsIconPressed = false;
+ await Task.Delay(10);
+ IsIconPressed = false;
#endif
- }
-
- ///
- /// This method perform adding a new view and removing the old view from the control.
- ///
- /// New View.
- /// Old View.
- void AddView(object oldValue, object newValue)
- {
- var oldView = (View)oldValue;
- if (oldView != null && this.Contains(oldView))
- {
- Remove(oldView);
- }
-
- var newView = (View)newValue;
- if (newView != null)
- {
- Add(newView);
- }
-
- UpdateLeadingViewVisibility(ShowLeadingView);
- UpdateTrailingViewVisibility(ShowTrailingView);
- }
-
- ///
- /// This method update the Content RectF.
- ///
- void UpdateContentPosition()
- {
- UpdateLeadViewWidthForContent();
- UpdateTrailViewWidthForContent();
-
- if (Content != null)
- {
- _viewBounds.X = (int)_leadViewWidth;
- _viewBounds.Y = 0;
- _viewBounds.Width = (int)(Width - _leadViewWidth - _trailViewWidth);
- _viewBounds.Height = (int)Height;
-
- if (EnablePasswordVisibilityToggle)
- {
- _viewBounds.Width -= (float)(EnablePasswordVisibilityToggle ? ((IconSize * 2) - RightPadding + DefaultAssistiveLabelPadding + 7) : (IconSize - RightPadding + DefaultAssistiveLabelPadding) + 3);
- }
-
- if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
- AbsoluteLayout.SetLayoutBounds(Content, _viewBounds);
- }
- }
-
- ///
- /// This method update the LeadingView RectF.
- ///
- void UpdateLeadingViewPosition()
- {
- if (ShowLeadingView && LeadingView != null)
- {
- _viewBounds.X = (float)(_leadingViewLeftPadding + ((IsOutlined && LeadingViewPosition == ViewPosition.Inside) ? BaseLineMaxHeight : 0));
- _viewBounds.Y = (float)(IsOutlined ? BaseLineMaxHeight : 0);
+ }
+
+ ///
+ /// This method perform adding a new view and removing the old view from the control.
+ ///
+ /// New View.
+ /// Old View.
+ void AddView(object oldValue, object newValue)
+ {
+ var oldView = (View)oldValue;
+ if (oldView != null && this.Contains(oldView))
+ {
+ Remove(oldView);
+ }
+
+ var newView = (View)newValue;
+ if (newView != null)
+ {
+ Add(newView);
+ }
+
+ UpdateLeadingViewVisibility(ShowLeadingView);
+ UpdateTrailingViewVisibility(ShowTrailingView);
+ }
+
+ ///
+ /// This method update the Content RectF.
+ ///
+ void UpdateContentPosition()
+ {
+ UpdateLeadViewWidthForContent();
+ UpdateTrailViewWidthForContent();
+
+ if (Content != null)
+ {
+ _viewBounds.X = (int)_leadViewWidth;
+ UpdatePosition();
+ _viewBounds.Y = 0;
+ _viewBounds.Width = (int)(Width - _leadViewWidth - _trailViewWidth);
+ _viewBounds.Height = (int)Height;
+
+ if (EnablePasswordVisibilityToggle)
+ {
+ _viewBounds.Width -= (float)(EnablePasswordVisibilityToggle ? ((IconSize * 2) - RightPadding + DefaultAssistiveLabelPadding + 7) : (IconSize - RightPadding + DefaultAssistiveLabelPadding) + 3);
+ }
+
+ if (IsClearIconVisible)
+ {
+ _viewBounds.Width -= (float)(IconSize - RightPadding + DefaultAssistiveLabelPadding) + 3;
+ }
+ if (ShowUpDownButton)
+ {
+ _viewBounds.Width -= (float)(IconSize * (IsUpDownVerticalAlignment ? 1 : 2));
+ }
+
+ if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
+ {
+ AbsoluteLayout.SetLayoutBounds(Content, _viewBounds);
+ }
+ }
+ }
+
+ void UpdatePosition()
+ {
+ if (!IsUpDownVerticalAlignment)
+ {
+ if (IsUpDownAlignmentLeft)
+ {
+ _viewBounds.X = !IsRTL ? _downIconRectF.X + (IsNone ? _downIconRectF.Width : _downIconRectF.Width / 2) : (float)this.Width - _downIconRectF.X - (IsNone ? 0 : _downIconRectF.Width / 2);
+ }
+ else if (IsUpDownAlignmentBoth)
+ {
+ _viewBounds.X = !IsRTL ? _upIconRectF.X + (IsNone ? _upIconRectF.Width : _upIconRectF.Width / 2) : (float)this.Width - _upIconRectF.X - (IsNone ? 0 : _upIconRectF.Width / 2);
+ }
+ }
+ else
+ {
+ if (IsUpDownAlignmentLeft)
+ {
+ _viewBounds.X = !IsRTL ? _downIconRectF.X + (IsNone ? _downIconRectF.Width : _downIconRectF.Width / 2) : (float)this.Width - _downIconRectF.X - (IsNone ? 0 : _downIconRectF.Width / 2);
+ }
+ }
+ }
+
+ ///
+ /// This method update the LeadingView RectF.
+ ///
+ void UpdateLeadingViewPosition()
+ {
+ if (ShowLeadingView && LeadingView != null)
+ {
+ _viewBounds.X = (float)(_leadingViewLeftPadding + ((IsOutlined && LeadingViewPosition == ViewPosition.Inside) ? BaseLineMaxHeight : 0));
+ _viewBounds.Y = (float)(IsOutlined ? BaseLineMaxHeight : 0);
#if WINDOWS || MACCATALYST || IOS
- _viewBounds.Width = (float)(LeadingView.WidthRequest == -1 ? (LeadingView.Width == -1 || (int)LeadingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width : LeadingView.WidthRequest);
+ _viewBounds.Width = (float)(LeadingView.WidthRequest == -1 ? (LeadingView.Width == -1 || (int)LeadingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width : LeadingView.WidthRequest);
#else
- _viewBounds.Width = (float)(LeadingView.WidthRequest == -1 ? (LeadingView.Width == -1 || LeadingView.Width == Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width : LeadingView.WidthRequest);
+ _viewBounds.Width = (float)(LeadingView.WidthRequest == -1 ? (LeadingView.Width == -1 || LeadingView.Width == Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width : LeadingView.WidthRequest);
#endif
- _viewBounds.Height = (float)(Height - AssistiveLabelPadding - TotalAssistiveTextHeight());
-
- if (IsOutlined || IsFilled)
- {
- LeadingView.VerticalOptions = LayoutOptions.Center;
- }
-
- if (IsNone)
- {
- _viewBounds.Height = (float)(_viewBounds.Height - BaseLineMaxHeight - NoneBottomPadding);
- LeadingView.VerticalOptions = LayoutOptions.End;
- }
-
- if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
- AbsoluteLayout.SetLayoutBounds(LeadingView, _viewBounds);
- }
- }
-
- ///
- /// This method update the TrailingView RectF.
- ///
- void UpdateTrailingViewPosition()
- {
- if (ShowTrailingView && TrailingView != null)
- {
+ _viewBounds.Height = (float)(Height - AssistiveLabelPadding - TotalAssistiveTextHeight());
+
+ if (IsOutlined || IsFilled)
+ {
+ LeadingView.VerticalOptions = LayoutOptions.Center;
+ }
+
+ if (IsNone)
+ {
+ _viewBounds.Height = (float)(_viewBounds.Height - BaseLineMaxHeight - NoneBottomPadding);
+ LeadingView.VerticalOptions = LayoutOptions.End;
+ }
+
+ if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
+ {
+ AbsoluteLayout.SetLayoutBounds(LeadingView, _viewBounds);
+ }
+ }
+ }
+
+ ///
+ /// This method update the TrailingView RectF.
+ ///
+ void UpdateTrailingViewPosition()
+ {
+ if (ShowTrailingView && TrailingView != null)
+ {
#if WINDOWS || MACCATALYST || IOS
- _viewBounds.Width = (float)(TrailingView.WidthRequest == -1 ? (TrailingView.Width == -1 || (int)TrailingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width : TrailingView.WidthRequest);
+ _viewBounds.Width = (float)(TrailingView.WidthRequest == -1 ? (TrailingView.Width == -1 || (int)TrailingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width : TrailingView.WidthRequest);
#else
- _viewBounds.Width = (float)(TrailingView.WidthRequest == -1 ? (TrailingView.Width == -1 || TrailingView.Width == Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width : TrailingView.WidthRequest);
+ _viewBounds.Width = (float)(TrailingView.WidthRequest == -1 ? (TrailingView.Width == -1 || TrailingView.Width == Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width : TrailingView.WidthRequest);
#endif
- _viewBounds.X = (float)(Width - _viewBounds.Width - _trailingViewRightPadding);
- _viewBounds.Y = (float)(IsOutlined ? BaseLineMaxHeight : 0);
- _viewBounds.Height = (float)(Height - AssistiveLabelPadding - TotalAssistiveTextHeight());
-
- if (IsOutlined || IsFilled)
- {
- TrailingView.VerticalOptions = LayoutOptions.Center;
- }
-
- if (IsNone)
- {
- _viewBounds.Height = (float)(_viewBounds.Height - BaseLineMaxHeight - NoneBottomPadding);
- TrailingView.VerticalOptions = LayoutOptions.End;
- }
-
- if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
- AbsoluteLayout.SetLayoutBounds(TrailingView, _viewBounds);
- }
- }
-
- ///
- /// This method changes the LeadingView visibility based on boolean parameter.
- ///
- /// Boolean.
- void UpdateLeadingViewVisibility(bool showLeadingView)
- {
- if (LeadingView != null)
- {
- LeadingView.IsVisible = showLeadingView;
- }
- }
-
- ///
- /// This method changes the TrailingView visibility based on boolean parameter.
- ///
- /// Boolean.
- void UpdateTrailingViewVisibility(bool showTrailingView)
- {
- if (TrailingView != null)
- {
- TrailingView.IsVisible = showTrailingView;
- }
- }
-
- ///
- /// This method updates the LeadingView width with their padding values.
- ///
- void UpdateLeadViewWidthForContent()
- {
- _leadViewWidth = 0;
-
- if (ShowLeadingView && LeadingView != null)
- {
+ _viewBounds.X = (float)(Width - _viewBounds.Width - _trailingViewRightPadding);
+ _viewBounds.Y = (float)(IsOutlined ? BaseLineMaxHeight : 0);
+ _viewBounds.Height = (float)(Height - AssistiveLabelPadding - TotalAssistiveTextHeight());
+
+ if (IsOutlined || IsFilled)
+ {
+ TrailingView.VerticalOptions = LayoutOptions.Center;
+ }
+
+ if (IsNone)
+ {
+ _viewBounds.Height = (float)(_viewBounds.Height - BaseLineMaxHeight - NoneBottomPadding);
+ TrailingView.VerticalOptions = LayoutOptions.End;
+ }
+
+ if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
+ {
+ AbsoluteLayout.SetLayoutBounds(TrailingView, _viewBounds);
+ }
+ }
+ }
+
+ ///
+ /// This method changes the LeadingView visibility based on boolean parameter.
+ ///
+ /// Boolean.
+ void UpdateLeadingViewVisibility(bool showLeadingView)
+ {
+ if (LeadingView != null)
+ {
+ LeadingView.IsVisible = showLeadingView;
+ }
+ }
+
+ ///
+ /// This method changes the TrailingView visibility based on boolean parameter.
+ ///
+ /// Boolean.
+ void UpdateTrailingViewVisibility(bool showTrailingView)
+ {
+ if (TrailingView != null)
+ {
+ TrailingView.IsVisible = showTrailingView;
+ }
+ }
+
+ ///
+ /// This method updates the LeadingView width with their padding values.
+ ///
+ void UpdateLeadViewWidthForContent()
+ {
+ _leadViewWidth = 0;
+
+ if (ShowLeadingView && LeadingView != null)
+ {
#if WINDOWS || MACCATALYST || IOS
- _leadViewWidth = ((LeadingView.Width == -1 || (int)LeadingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width) + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
+ _leadViewWidth = ((LeadingView.Width == -1 || (int)LeadingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width) + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
#else
- _leadViewWidth = ((LeadingView.Width == -1 || LeadingView.Width == Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width) + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
+ _leadViewWidth = ((LeadingView.Width == -1 || LeadingView.Width == Width) ? _defaultLeadAndTrailViewWidth : LeadingView.Width) + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
#endif
- }
- }
-
- ///
- /// This method updates the TrailingView width with their padding values.
- ///
- void UpdateTrailViewWidthForContent()
- {
- _trailViewWidth = 0;
-
- if (ShowTrailingView && TrailingView != null)
- {
+ }
+ }
+
+ ///
+ /// This method updates the TrailingView width with their padding values.
+ ///
+ void UpdateTrailViewWidthForContent()
+ {
+ _trailViewWidth = 0;
+
+ if (ShowTrailingView && TrailingView != null)
+ {
#if WINDOWS || MACCATALYST || IOS
- _trailViewWidth = ((TrailingView.Width == -1 || (int)TrailingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width) + (TrailingViewPosition == ViewPosition.Outside ? _trailingViewLeftPadding + _trailingViewRightPadding : _trailingViewLeftPadding);
-#else
- _trailViewWidth = ((TrailingView.Width == -1 || TrailingView.Width == Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width) + (TrailingViewPosition == ViewPosition.Outside ? _trailingViewLeftPadding + _trailingViewRightPadding : _trailingViewLeftPadding);
-#endif
- }
- }
-
- ///
- /// This method update the leading view width only in leading view position is outside.
- ///
- void UpdateLeadViewWidthForBorder()
- {
- _leadViewWidth = 0;
- if (ShowLeadingView && LeadingView != null && LeadingViewPosition == ViewPosition.Outside)
- {
- _leadViewWidth = LeadingView.Width + _leadingViewLeftPadding + _leadingViewRightPadding;
- }
- }
-
- ///
- /// This method update the leading view width only in leading view position is outside.
- ///
- void UpdateTrailViewWidthForBorder()
- {
- _trailViewWidth = 0;
- if (ShowTrailingView && TrailingView != null && TrailingViewPosition == ViewPosition.Outside)
- {
- _trailViewWidth = TrailingView.Width + _trailingViewLeftPadding + _trailingViewRightPadding;
- }
- }
-
- ///
- /// This method updates the effects tilRenderer rectF.
- ///
- void UpdateEffectsRendererBounds()
- {
- if (_effectsRenderer != null)
- {
- _effectsRenderer.RippleBoundsCollection.Clear();
- _effectsRenderer.HighlightBoundsCollection.Clear();
-
- if (IsPassowordToggleIconVisible)
- {
- _effectsRenderer.RippleBoundsCollection.Add(_passwordToggleIconRectF);
- _effectsRenderer.HighlightBoundsCollection.Add(_passwordToggleIconRectF);
- }
- }
- }
-
- ///
- /// This method update the counter text string value.
- ///
- void UpdateCounterLabelText()
- {
- if (ShowCharCount)
- {
- var textLength = string.IsNullOrEmpty(Text) ? 0 : Text.Length;
- _counterText = CharMaxLength == int.MaxValue ? $"{textLength}" : $"{textLength}/{CharMaxLength}";
- InvalidateDrawable();
- }
- }
-
- ///
- /// This method updates the hint text position in none type container.
- ///
- void UpdateNoneContainerHintPosition()
- {
- if (IsHintFloated)
- {
- _hintRect.X = 0;
- _hintRect.Y = (float)DefaultAssistiveLabelPadding;
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = FloatedHintSize.Height;
- }
- else
- {
- _hintRect.X = 0;
- _hintRect.Y = (int)(Height - HintSize.Height - BaseLineMaxHeight - AssistiveLabelPadding - (DefaultAssistiveLabelPadding * 2) - (ErrorTextSize.Height > HelperTextSize.Height ? ErrorTextSize.Height : HelperTextSize.Height));
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = HintSize.Height;
- }
- }
-
- ///
- /// This method updates the hint text position in filled type container.
- ///
- void UpdateFilledContainerHintPosition()
- {
- if (IsHintFloated)
- {
- _hintRect.X = (float)((StartX) + DefaultAssistiveLabelPadding);
- _hintRect.Y = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = FloatedHintSize.Height;
- }
- else
- {
- _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding);
- _hintRect.Y = (float)((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = HintSize.Height;
- }
- }
-
- ///
- /// This method updates the hint text position in outlined type container.
- ///
- void UpdateOutlinedContainerHintPosition()
- {
- if (IsHintFloated)
- {
- _hintRect.X = (int)(StartX + DefaultAssistiveLabelPadding + BaseLineMaxHeight);
- if (BaseLineMaxHeight > 2)
- _hintRect.X = (int)(StartX + DefaultAssistiveLabelPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2));
- _hintRect.Y = 0;
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = (int)FloatedHintSize.Height;
- }
- else
- {
- _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding + BaseLineMaxHeight);
- if (BaseLineMaxHeight > 2)
- _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2));
- _hintRect.Y = _outlineRectF.Center.Y - (HintSize.Height / 2);
- _hintRect.Width = (float)GetHintTextWidth();
- _hintRect.Height = HintSize.Height;
- }
- }
-
- ///
- /// This method updates the starting point of the hint text need.
- ///
- void UpdateHintPosition()
- {
- if (_isAnimating)
- {
- return;
- }
-
- if (IsNone)
- {
- UpdateNoneContainerHintPosition();
- }
-
- if (IsFilled)
- {
- UpdateFilledContainerHintPosition();
- }
-
- if (IsOutlined)
- {
- UpdateOutlinedContainerHintPosition();
- }
-
- if (ShowLeadingView && LeadingView != null)
- {
- _leadViewWidth = LeadingView.Width + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
- _hintRect.X += (float)_leadViewWidth;
- }
-
- if (IsRTL)
- {
- _hintRect.X = (float)(Width - _hintRect.X - _hintRect.Width);
- }
- }
-
- ///
- /// This method updates the starting point of the assistive text.
- ///
- void UpdateHelperTextPosition()
- {
- UpdateLeadViewWidthForContent();
- UpdateTrailViewWidthForBorder();
- var startPadding = IsNone ? 0 : StartX + DefaultAssistiveLabelPadding + (IsOutlined ? (BaseLineMaxHeight) : 0);
- _helperTextRect.X = (int)(startPadding + _leadViewWidth);
- if (BaseLineMaxHeight <= 2)
- _helperTextRect.Y = (int)(Height - TotalAssistiveTextHeight() - BaseLineMaxHeight / 2);
- else
- _helperTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
- _helperTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
- _helperTextRect.Height = HelperTextSize.Height;
-
- if (IsRTL)
- {
- _helperTextRect.X = (int)(Width - _helperTextRect.X - _helperTextRect.Width);
- }
-
- }
-
- double GetAssistiveTextWidth()
- {
- UpdateLeadViewWidthForContent();
- UpdateTrailViewWidthForBorder();
-
- if (Width >= 0)
- {
- return Width - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
- }
- else if (WidthRequest != -1)
- {
- return WidthRequest - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
- }
- else
- {
- return 1;
- }
- }
-
-
- double GetHintTextWidth()
- {
- UpdateLeadViewWidthForContent();
- if (IsHintFloated) { UpdateTrailViewWidthForBorder(); } else { UpdateTrailViewWidthForContent(); }
-
-
- if (Width >= 0)
- {
- return Width - (IsNone ? 0 : ((2 * StartX) + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
- }
- else if (WidthRequest != -1)
- {
- return WidthRequest - (IsNone ? 0 : 2 * (StartX + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
- }
- else
- {
- return 1;
- }
- }
-
- ///
- /// This method updates the starting point of the assistive text.
- ///
- void UpdateErrorTextPosition()
- {
- UpdateLeadViewWidthForContent();
- UpdateTrailViewWidthForBorder();
- var startPadding = IsNone ? 0 : StartX + DefaultAssistiveLabelPadding + (IsOutlined ? (BaseLineMaxHeight) : 0);
- _errorTextRect.X = (int)(startPadding + _leadViewWidth);
- if (BaseLineMaxHeight <= 2)
- _errorTextRect.Y = (int)(Height - TotalAssistiveTextHeight() - BaseLineMaxHeight / 2);
- else
- _errorTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
- _errorTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
- _errorTextRect.Height = ErrorTextSize.Height;
-
- if (IsRTL)
- {
- _errorTextRect.X = (int)(Width - _errorTextRect.X - _errorTextRect.Width);
- }
-
- }
-
- ///
- /// This method updates the starting point of the counter.
- ///
- void UpdateCounterTextPosition()
- {
- UpdateTrailViewWidthForBorder();
- _counterTextRect.X = (int)(Width - CounterTextSize.Width - CounterTextPadding - _trailViewWidth);
- _counterTextRect.Y = (int)(Height - CounterTextSize.Height);
- _counterTextRect.Width = (int)CounterTextSize.Width;
- _counterTextRect.Height = (int)CounterTextSize.Height;
-
- if (IsRTL)
- {
- _counterTextRect.X = (float)(Width - _counterTextRect.X - _counterTextRect.Width);
- }
- }
-
- ///
- /// This method updates the start point and end point of the base line in filled and none type container.
- ///
- void UpdateBaseLinePoints()
- {
- UpdateLeadViewWidthForBorder();
- _startPoint.X = (float)_leadViewWidth;
- _startPoint.Y = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
- UpdateTrailViewWidthForBorder();
- _endPoint.X = (float)(Width - _trailViewWidth);
- _endPoint.Y = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
- }
-
- ///
- /// This method updates the rectF of the border line in outlined type container.
- ///
- void UpdateOutlineRectF()
- {
- UpdateLeadViewWidthForBorder();
- if (BaseLineMaxHeight <= 2)
- {
- _outlineRectF.X = (float)(BaseLineMaxHeight + _leadViewWidth);
- _outlineRectF.Y = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height / 2);
- }
- else
- {
- _outlineRectF.X = (float)((BaseLineMaxHeight / 2) + _leadViewWidth);
- _outlineRectF.Y = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight / 2 : FloatedHintSize.Height / 2);
- }
- UpdateLeadViewWidthForBorder();
- UpdateTrailViewWidthForBorder();
- if (BaseLineMaxHeight <= 2)
- {
- _outlineRectF.Width = (float)((Width - (BaseLineMaxHeight * 2)) - _leadViewWidth - _trailViewWidth);
- }
- else
- {
- _outlineRectF.Width = (float)((Width - (BaseLineMaxHeight)) - _leadViewWidth - _trailViewWidth);
- }
-
- _outlineRectF.Height = (float)(Height - _outlineRectF.Y - TotalAssistiveTextHeight() - AssistiveLabelPadding);
- }
-
- void UpdateOutlineBackgroundRectF()
- {
- if (IsLayoutFocused)
- {
- _backgroundRectF.X = (float)(_outlineRectF.X + (FocusedStrokeThickness / 2));
- _backgroundRectF.Y = (float)(_outlineRectF.Y + (FocusedStrokeThickness / 2));
- _backgroundRectF.Width = (float)(_outlineRectF.Width - (FocusedStrokeThickness));
- _backgroundRectF.Height = (float)(_outlineRectF.Height - (FocusedStrokeThickness));
- }
- else
- {
- _backgroundRectF.X = (float)(_outlineRectF.X + (UnfocusedStrokeThickness / 2));
- _backgroundRectF.Y = (float)(_outlineRectF.Y + (UnfocusedStrokeThickness / 2));
- _backgroundRectF.Width = (float)(_outlineRectF.Width - (UnfocusedStrokeThickness));
- _backgroundRectF.Height = (float)(_outlineRectF.Height - (UnfocusedStrokeThickness));
- }
-
- }
-
- ///
- /// This method updates the rectF of the background color in filled type container.
- ///
- void UpdateBackgroundRectF()
- {
- UpdateLeadViewWidthForBorder();
- UpdateTrailViewWidthForBorder();
- _backgroundRectF.X = (float)_leadViewWidth;
- _backgroundRectF.Y = 0;
- _backgroundRectF.Width = (float)(Width - _leadViewWidth - _trailViewWidth);
- if (BaseLineMaxHeight <= 2)
- _backgroundRectF.Height = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
- else
- _backgroundRectF.Height = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding - (IsLayoutFocused ? FocusedStrokeThickness / 2 : UnfocusedStrokeThickness / 2));
- }
-
- ///
- /// This method updates the rectF of the floated hint text space in outlined type container.
- ///
- void CalculateClipRect()
- {
- if (!string.IsNullOrEmpty(Hint) && EnableFloating)
- {
- if (BaseLineMaxHeight <= 2)
- _clipRect.X = _outlineRectF.X + StartX;
- else
- _clipRect.X = (float)(_outlineRectF.X + StartX + (BaseLineMaxHeight / 2));
- _clipRect.Y = 0;
- _clipRect.Width = (float)Math.Min((FloatedHintSize.Width + DefaultAssistiveLabelPadding + DefaultAssistiveLabelPadding), GetHintTextWidth());
- if (BaseLineMaxHeight <= 2)
- _clipRect.Height = FloatedHintSize.Height;
- else
- _clipRect.Height = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height);
- if (ShowLeadingView && LeadingView != null && LeadingViewPosition == ViewPosition.Inside)
- {
- _clipRect.X = (float)(_clipRect.X + LeadingView.Width + _leadingViewLeftPadding);
- }
- }
- else
- {
- _clipRect = new Rect(0, 0, 0, 0);
- }
- }
-
- ///
- /// This method updates the downIconRectF and clearIconRectF.
- ///
- void UpdateIconRectF()
- {
- UpdateOutlineRectF();
- UpdateBackgroundRectF();
- UpdateTrailViewWidthForContent();
- UpdatePasswordToggleIconRectF();
- UpdateEffectsRendererBounds();
- }
-
- ///
- /// This method calculate the password toggle icon rectF position.
- ///
- void UpdatePasswordToggleIconRectF()
- {
- _passwordToggleIconRectF.X = (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
- if (IsNone && Content != null)
- {
- _passwordToggleIconRectF.Y = (float)((Content.Y + (Content.Height / 2)) - (UpDownButtonSize / 2));
- }
- else if (IsOutlined)
- {
- _passwordToggleIconRectF.Y = ((_outlineRectF.Center.Y) - (UpDownButtonSize / 2));
- }
- else
- {
- _passwordToggleIconRectF.Y = (float)(((Height - AssistiveLabelPadding - TotalAssistiveTextHeight()) / 2) - (UpDownButtonSize / 2));
- }
-
- _passwordToggleIconRectF.Width = IsPassowordToggleIconVisible ? UpDownButtonSize : 0;
- _passwordToggleIconRectF.Height = IsPassowordToggleIconVisible ? UpDownButtonSize : 0;
-
- if (IsRTL)
- {
- _passwordToggleIconRectF.X = (float)(Width - _passwordToggleIconRectF.X - _passwordToggleIconRectF.Width);
- }
-
- }
-
- ///
- /// Updates the hint color of UI elements based on state.
- ///
- void UpdateHintColor()
- {
- if (!HintLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
- {
- _internalHintLabelStyle.TextColor = HintLabelStyle.TextColor;
- }
- else
- {
- if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme))
- {
- _internalHintLabelStyle.TextColor = IsEnabled ? ((IsLayoutFocused || HasError) && Stroke != null) ? Stroke : HintLabelStyle.TextColor : DisabledColor;
- }
- else
- {
- if (IsEnabled)
- {
- if (IsLayoutFocused)
- {
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutFocusedHintTextColor");
- _internalHintLabelStyle.TextColor = HintTextColor;
- }
- else
- {
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutHintTextColor");
- _internalHintLabelStyle.TextColor = HintTextColor;
- }
-
- }
- else
- {
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutDisabledHintTextColor");
- _internalHintLabelStyle.TextColor = HintTextColor;
- }
- }
- }
- }
-
- ///
- /// Updates the helper color of UI elements based on state.
- ///
- void UpdateHelperTextColor()
- {
- if (!HelperLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
- {
- _internalHelperLabelStyle.TextColor = HelperLabelStyle.TextColor;
- }
- else
- {
- if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme))
- {
- _internalHelperLabelStyle.TextColor = IsEnabled ? ((HasError) && Stroke != null) ? Stroke : HelperLabelStyle.TextColor : DisabledColor;
- }
- else
- {
- if (IsEnabled)
- {
- SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutHelperTextColor");
- _internalHelperLabelStyle.TextColor = HelperTextColor;
- }
- else
- {
- SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutDisabledHelperTextColor");
- _internalHelperLabelStyle.TextColor = HelperTextColor;
- }
- }
- }
- }
-
- ///
- /// Updates the error color of UI elements based on state.
- ///
- void UpdateErrorTextColor()
- {
- _internalErrorLabelStyle.TextColor = IsEnabled ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : DisabledColor;
- }
-
- ///
- /// Updates the counter color of UI elements based on state.
- ///
- void UpdateCounterTextColor()
- {
- _internalCounterLabelStyle.TextColor = IsEnabled ? HasError ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : CounterLabelStyle.TextColor : DisabledColor;
- }
-
- ///
- /// Check the stroke value is set or not.
- ///
- bool HasStrokeValue()
- {
- return Stroke != null && !Stroke.Equals(_defaultStrokeColor);
- }
-
- ///
- /// Check the container background value is set or not.
- ///
- bool HasContainerBackgroundValue()
- {
- if (ContainerBackground is not SolidColorBrush)
- {
- return true;
- }
-
- if (ContainerBackground is SolidColorBrush solidColorBrush && !solidColorBrush.Color.Equals(_defaultContainerBackground))
- {
- return true;
- }
-
- return false;
- }
-
- void DrawBorder(ICanvas canvas, RectF dirtyRect)
- {
- canvas.CanvasSaveState();
- if (IsRTL)
- {
- canvas.Translate((float)Width, 0);
- canvas.Scale(-1, 1);
- }
- canvas.StrokeSize = (float)(IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness);
-
- SetStrokeColor(canvas);
-
- if (!IsOutlined)
- {
- DrawFilledOrNoneBorder(canvas);
- }
- else
- {
- DrawOutlinedBorder(canvas);
- }
-
- canvas.CanvasRestoreState();
- }
-
- void SetStrokeColor(ICanvas canvas)
- {
- if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme1) && !VisualStateManager.HasVisualStateGroups(this))
- {
- canvas.StrokeColor = IsEnabled ? Stroke : DisabledColor;
- }
- else
- {
- canvas.StrokeColor = Stroke;
- }
- }
-
- void DrawFilledOrNoneBorder(ICanvas canvas)
- {
- UpdateBaseLinePoints();
- if (IsFilled)
- {
- canvas.CanvasSaveState();
- if (ContainerBackground is SolidColorBrush backgroundColor)
- canvas.FillColor = backgroundColor.Color;
- UpdateBackgroundRectF();
- canvas.FillRoundedRectangle(_backgroundRectF, OutlineCornerRadius, OutlineCornerRadius, 0, 0);
- canvas.CanvasRestoreState();
- }
-
- if ((IsLayoutFocused && FocusedStrokeThickness > 0) || (!IsLayoutFocused && UnfocusedStrokeThickness > 0))
- {
- canvas.DrawLine(_startPoint.X, _startPoint.Y, _endPoint.X, _endPoint.Y);
- }
- }
-
- void DrawOutlinedBorder(ICanvas canvas)
- {
- UpdateOutlineRectF();
- if (BaseLineMaxHeight > 2)
- UpdateOutlineBackgroundRectF();
-
- if (((IsLayoutFocused && !string.IsNullOrEmpty(Hint)) || IsHintFloated) && ShowHint)
- {
- CalculateClipRect();
- canvas.SubtractFromClip(_clipRect);
- }
-
- SetOutlinedContainerBackground(canvas);
-
- if (OutlineCornerRadius != 0)
- {
- DrawRoundedOutlinedBorder(canvas);
- }
- else
- {
- canvas.FillRectangle(_backgroundRectF);
- if ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) > 0)
- {
- canvas.DrawRectangle(_outlineRectF);
- }
- }
- }
-
- void SetOutlinedContainerBackground(ICanvas canvas)
- {
- if (Application.Current != null && Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme))
- {
- SetDynamicResource(OutlinedContainerBackgroundProperty, "SfTextInputLayoutOutlinedContainerBackground");
- if (OutlinedContainerBackground is SolidColorBrush backgroundColor)
- {
- canvas.FillColor = backgroundColor.Color;
- }
- }
- else
- {
- if (ContainerBackground is SolidColorBrush backgroundColor)
- {
- canvas.FillColor = backgroundColor.Color;
- }
- }
- }
-
- void DrawRoundedOutlinedBorder(ICanvas canvas)
- {
- if (BaseLineMaxHeight <= 2)
- {
- canvas.FillRoundedRectangle(_outlineRectF, OutlineCornerRadius);
- }
- else
- {
- var cornerRadius = ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) / 2) < OutlineCornerRadius ? OutlineCornerRadius - ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) / 2) : 0;
- canvas.FillRoundedRectangle(_backgroundRectF, cornerRadius);
- }
- if ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) > 0)
- {
- canvas.DrawRoundedRectangle(_outlineRectF, OutlineCornerRadius);
- }
- }
-
- void DrawHintText(ICanvas canvas, RectF dirtyRect)
- {
- if ((IsHintFloated && !EnableFloating) || (!IsHintFloated && !string.IsNullOrEmpty(Text)))
- {
- return;
- }
-
- if (ShowHint && !string.IsNullOrEmpty(Hint) && HintLabelStyle != null)
- {
- canvas.CanvasSaveState();
- UpdateOutlineRectF();
- UpdateHintPosition();
- UpdateHintColor();
-
- _internalHintLabelStyle.FontSize = _isAnimating ? (float)_animatingFontSize : IsHintFloated ? FloatedHintFontSize : HintLabelStyle.FontSize;
-
- HorizontalAlignment horizontalAlignment = IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left;
-#if IOS || MACCATALYST
- VerticalAlignment verticalAlignment = VerticalAlignment.Top;
+ _trailViewWidth = ((TrailingView.Width == -1 || (int)TrailingView.Width == (int)Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width) + (TrailingViewPosition == ViewPosition.Outside ? _trailingViewLeftPadding + _trailingViewRightPadding : _trailingViewLeftPadding);
#else
- VerticalAlignment verticalAlignment = VerticalAlignment.Center;
+ _trailViewWidth = ((TrailingView.Width == -1 || TrailingView.Width == Width) ? _defaultLeadAndTrailViewWidth : TrailingView.Width) + (TrailingViewPosition == ViewPosition.Outside ? _trailingViewLeftPadding + _trailingViewRightPadding : _trailingViewLeftPadding);
#endif
+ }
+ }
+
+ ///
+ /// This method update the leading view width only in leading view position is outside.
+ ///
+ void UpdateLeadViewWidthForBorder()
+ {
+ _leadViewWidth = 0;
+ if (ShowLeadingView && LeadingView != null && LeadingViewPosition == ViewPosition.Outside)
+ {
+ _leadViewWidth = LeadingView.Width + _leadingViewLeftPadding + _leadingViewRightPadding;
+ }
+ }
+
+ ///
+ /// This method update the leading view width only in leading view position is outside.
+ ///
+ void UpdateTrailViewWidthForBorder()
+ {
+ _trailViewWidth = 0;
+ if (ShowTrailingView && TrailingView != null && TrailingViewPosition == ViewPosition.Outside)
+ {
+ _trailViewWidth = TrailingView.Width + _trailingViewLeftPadding + _trailingViewRightPadding;
+ }
+ }
+
+ ///
+ /// This method updates the effects tilRenderer rectF.
+ ///
+ void UpdateEffectsRendererBounds()
+ {
+ if (_effectsRenderer != null)
+ {
+ _effectsRenderer.RippleBoundsCollection.Clear();
+ _effectsRenderer.HighlightBoundsCollection.Clear();
+
+ if (IsPassowordToggleIconVisible)
+ {
+ _effectsRenderer.RippleBoundsCollection.Add(_passwordToggleIconRectF);
+ _effectsRenderer.HighlightBoundsCollection.Add(_passwordToggleIconRectF);
+ }
+
+ if (ShowUpDownButton)
+ {
+ _effectsRenderer.RippleBoundsCollection.Add(_downIconRectF);
+ _effectsRenderer.HighlightBoundsCollection.Add(_downIconRectF);
+ _effectsRenderer.RippleBoundsCollection.Add(_upIconRectF);
+ _effectsRenderer.HighlightBoundsCollection.Add(_upIconRectF);
+ }
+
+ if (IsClearIconVisible && (IsLayoutFocused))
+ {
+ _effectsRenderer.RippleBoundsCollection.Add(_clearIconRectF);
+ _effectsRenderer.HighlightBoundsCollection.Add(_clearIconRectF);
+ }
+ }
+ }
+
+ ///
+ /// This method update the counter text string value.
+ ///
+ void UpdateCounterLabelText()
+ {
+ if (ShowCharCount)
+ {
+ var textLength = string.IsNullOrEmpty(Text) ? 0 : Text.Length;
+ _counterText = CharMaxLength == int.MaxValue ? $"{textLength}" : $"{textLength}/{CharMaxLength}";
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// This method updates the hint text position in none type container.
+ ///
+ void UpdateNoneContainerHintPosition()
+ {
+ if (IsHintFloated)
+ {
+ _hintRect.X = 0;
+ _hintRect.Y = (float)DefaultAssistiveLabelPadding;
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = FloatedHintSize.Height;
+ }
+ else
+ {
+ _hintRect.X = 0;
+ _hintRect.Y = (int)(Height - HintSize.Height - BaseLineMaxHeight - AssistiveLabelPadding - (DefaultAssistiveLabelPadding * 2) - (ErrorTextSize.Height > HelperTextSize.Height ? ErrorTextSize.Height : HelperTextSize.Height));
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = HintSize.Height;
+ }
+ }
+
+
+ ///
+ /// This method updates the hint text position in filled type container.
+ ///
+ void UpdateFilledContainerHintPosition()
+ {
+ if (IsHintFloated)
+ {
+ _hintRect.X = (float)((StartX) + DefaultAssistiveLabelPadding);
+ _hintRect.Y = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = FloatedHintSize.Height;
+ }
+ else
+ {
+ _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding);
+ _hintRect.Y = (float)((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = HintSize.Height;
+ }
+ }
+
+ ///
+ /// This method updates the hint text position in outlined type container.
+ ///
+ void UpdateOutlinedContainerHintPosition()
+ {
+ if (IsHintFloated)
+ {
+ _hintRect.X = (int)(StartX + DefaultAssistiveLabelPadding + BaseLineMaxHeight);
+ if (BaseLineMaxHeight > 2)
+ {
+ _hintRect.X = (int)(StartX + DefaultAssistiveLabelPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2));
+ }
+
+ _hintRect.Y = 0;
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = (int)FloatedHintSize.Height;
+ }
+ else
+ {
+ _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding + BaseLineMaxHeight);
+ if (BaseLineMaxHeight > 2)
+ {
+ _hintRect.X = (float)(StartX + DefaultAssistiveLabelPadding + (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2));
+ }
+
+ _hintRect.Y = _outlineRectF.Center.Y - (HintSize.Height / 2);
+ _hintRect.Width = (float)GetHintTextWidth();
+ _hintRect.Height = HintSize.Height;
+ }
+ }
+
+ ///
+ /// This method updates the starting point of the hint text need.
+ ///
+ void UpdateHintPosition()
+ {
+ if (_isAnimating)
+ {
+ return;
+ }
+
+ if (IsNone)
+ {
+ UpdateNoneContainerHintPosition();
+ }
+
+ if (IsFilled)
+ {
+ UpdateFilledContainerHintPosition();
+ }
+
+ if (IsOutlined)
+ {
+ UpdateOutlinedContainerHintPosition();
+ }
+
+ if (ShowLeadingView && LeadingView != null)
+ {
+ _leadViewWidth = LeadingView.Width + (LeadingViewPosition == ViewPosition.Outside ? _leadingViewLeftPadding + _leadingViewRightPadding : IsNone ? _leadingViewLeftPadding + _leadingViewRightPadding : _leadingViewLeftPadding);
+ _hintRect.X += (float)_leadViewWidth;
+ }
+
+ if (ShowUpDownButton && (IsUpDownAlignmentLeft || IsUpDownAlignmentBoth) && ((IsUpDownVerticalAlignment && !IsUpDownAlignmentBoth) || !IsUpDownVerticalAlignment))
+ {
+ if ((IsUpDownAlignmentBoth && !IsUpDownVerticalAlignment) || (IsUpDownVerticalAlignment && !IsUpDownAlignmentBoth))
+ {
+ _hintRect.X += _upIconRectF.Width ;
+ }
+ else
+ {
+ _hintRect.X += _upIconRectF.Width * 2;
+ }
+ if (IsNone)
+ {
+ _hintRect.X += (float)BaseWidth();
+ }
+ else
+ {
+ _hintRect.X -= (float)BaseWidth();
+ }
+ }
+
+ if (IsRTL)
+ {
+ _hintRect.X = (float)(Width - _hintRect.X - _hintRect.Width);
+ }
+ }
+
+ ///
+ /// This method updates the starting point of the assistive text.
+ ///
+ void UpdateHelperTextPosition()
+ {
+ UpdateLeadViewWidthForContent();
+ UpdateTrailViewWidthForBorder();
+ var startPadding = IsNone ? 0 : StartX + DefaultAssistiveLabelPadding + (IsOutlined ? (BaseLineMaxHeight) : 0);
+ _helperTextRect.X = (int)(startPadding + _leadViewWidth);
+ if (BaseLineMaxHeight <= 2)
+ {
+ _helperTextRect.Y = (int)(Height - TotalAssistiveTextHeight() - BaseLineMaxHeight / 2);
+ }
+ else
+ {
+ _helperTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
+ }
+
+ _helperTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
+ _helperTextRect.Height = HelperTextSize.Height;
+
+ if (IsRTL)
+ {
+ _helperTextRect.X = (int)(Width - _helperTextRect.X - _helperTextRect.Width);
+ }
+
+ }
+
+ double GetAssistiveTextWidth()
+ {
+ UpdateLeadViewWidthForContent();
+ UpdateTrailViewWidthForBorder();
+
+ if (Width >= 0)
+ {
+ return Width - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
+ }
+ else if (WidthRequest != -1)
+ {
+ return WidthRequest - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+
+ double GetHintTextWidth()
+ {
+ UpdateLeadViewWidthForContent();
+ if (IsHintFloated)
+ { UpdateTrailViewWidthForBorder(); }
+ else
+ { UpdateTrailViewWidthForContent(); }
+
+
+ if (Width >= 0)
+ {
+ return Width - (IsNone ? 0 : ((2 * StartX) + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
+ }
+ else if (WidthRequest != -1)
+ {
+ return WidthRequest - (IsNone ? 0 : 2 * (StartX + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ ///
+ /// This method updates the starting point of the assistive text.
+ ///
+ void UpdateErrorTextPosition()
+ {
+ UpdateLeadViewWidthForContent();
+ UpdateTrailViewWidthForBorder();
+ var startPadding = IsNone ? 0 : StartX + DefaultAssistiveLabelPadding + (IsOutlined ? (BaseLineMaxHeight) : 0);
+ _errorTextRect.X = (int)(startPadding + _leadViewWidth);
+ if (BaseLineMaxHeight <= 2)
+ {
+ _errorTextRect.Y = (int)(Height - TotalAssistiveTextHeight() - BaseLineMaxHeight / 2);
+ }
+ else
+ {
+ _errorTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
+ }
+
+ _errorTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
+ _errorTextRect.Height = ErrorTextSize.Height;
+
+ if (IsRTL)
+ {
+ _errorTextRect.X = (int)(Width - _errorTextRect.X - _errorTextRect.Width);
+ }
+
+ }
+
+ ///
+ /// This method updates the starting point of the counter.
+ ///
+ void UpdateCounterTextPosition()
+ {
+ UpdateTrailViewWidthForBorder();
+ _counterTextRect.X = (int)(Width - CounterTextSize.Width - CounterTextPadding - _trailViewWidth);
+ _counterTextRect.Y = (int)(Height - CounterTextSize.Height);
+ _counterTextRect.Width = (int)CounterTextSize.Width;
+ _counterTextRect.Height = (int)CounterTextSize.Height;
+
+ if (IsRTL)
+ {
+ _counterTextRect.X = (float)(Width - _counterTextRect.X - _counterTextRect.Width);
+ }
+ }
+
+ ///
+ /// This method updates the start point and end point of the base line in filled and none type container.
+ ///
+ void UpdateBaseLinePoints()
+ {
+ UpdateLeadViewWidthForBorder();
+ _startPoint.X = (float)_leadViewWidth;
+ _startPoint.Y = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
+ UpdateTrailViewWidthForBorder();
+ _endPoint.X = (float)(Width - _trailViewWidth);
+ _endPoint.Y = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
+ }
+
+ ///
+ /// This method updates the rectF of the border line in outlined type container.
+ ///
+ void UpdateOutlineRectF()
+ {
+ UpdateLeadViewWidthForBorder();
+ if (BaseLineMaxHeight <= 2)
+ {
+ _outlineRectF.X = (float)(BaseLineMaxHeight + _leadViewWidth);
+ _outlineRectF.Y = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height / 2);
+ }
+ else
+ {
+ _outlineRectF.X = (float)((BaseLineMaxHeight / 2) + _leadViewWidth);
+ _outlineRectF.Y = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight / 2 : FloatedHintSize.Height / 2);
+ }
+ UpdateLeadViewWidthForBorder();
+ UpdateTrailViewWidthForBorder();
+ if (BaseLineMaxHeight <= 2)
+ {
+ _outlineRectF.Width = (float)((Width - (BaseLineMaxHeight * 2)) - _leadViewWidth - _trailViewWidth);
+ }
+ else
+ {
+ _outlineRectF.Width = (float)((Width - (BaseLineMaxHeight)) - _leadViewWidth - _trailViewWidth);
+ }
+
+ _outlineRectF.Height = (float)(Height - _outlineRectF.Y - TotalAssistiveTextHeight() - AssistiveLabelPadding);
+ }
+
+ void UpdateOutlineBackgroundRectF()
+ {
+ if (IsLayoutFocused)
+ {
+ _backgroundRectF.X = (float)(_outlineRectF.X + (FocusedStrokeThickness / 2));
+ _backgroundRectF.Y = (float)(_outlineRectF.Y + (FocusedStrokeThickness / 2));
+ _backgroundRectF.Width = (float)(_outlineRectF.Width - (FocusedStrokeThickness));
+ _backgroundRectF.Height = (float)(_outlineRectF.Height - (FocusedStrokeThickness));
+ }
+ else
+ {
+ _backgroundRectF.X = (float)(_outlineRectF.X + (UnfocusedStrokeThickness / 2));
+ _backgroundRectF.Y = (float)(_outlineRectF.Y + (UnfocusedStrokeThickness / 2));
+ _backgroundRectF.Width = (float)(_outlineRectF.Width - (UnfocusedStrokeThickness));
+ _backgroundRectF.Height = (float)(_outlineRectF.Height - (UnfocusedStrokeThickness));
+ }
+
+ }
+
+ ///
+ /// This method updates the rectF of the background color in filled type container.
+ ///
+ void UpdateBackgroundRectF()
+ {
+ UpdateLeadViewWidthForBorder();
+ UpdateTrailViewWidthForBorder();
+ _backgroundRectF.X = (float)_leadViewWidth;
+ _backgroundRectF.Y = 0;
+ _backgroundRectF.Width = (float)(Width - _leadViewWidth - _trailViewWidth);
+ if (BaseLineMaxHeight <= 2)
+ {
+ _backgroundRectF.Height = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);
+ }
+ else
+ {
+ _backgroundRectF.Height = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding - (IsLayoutFocused ? FocusedStrokeThickness / 2 : UnfocusedStrokeThickness / 2));
+ }
+ }
+
+ ///
+ /// Adjust the hint rect clip X position based on the spin button alignment
+ ///
+ /// returns spin button width
+ double GetAdditionalWidth()
+ {
+ double tempWidth = 0;
+ if (ShowUpDownButton)
+ {
+ if ((IsUpDownVerticalAlignment && IsUpDownAlignmentLeft) || (!IsUpDownVerticalAlignment && IsUpDownAlignmentBoth))
+ {
+ tempWidth = IconSize - BaseWidth();
+ }
+ else
+ {
+ if (IsUpDownAlignmentLeft && !IsUpDownVerticalAlignment)
+ {
+ tempWidth = (IconSize * 2) - BaseWidth();
+ }
+ }
+ }
+ return tempWidth;
+ }
+
+ ///
+ /// Calculate the starting X point for hint rect
+ ///
+ /// returns the starting position
+ double BaseWidth()
+ {
+ return BaseLineMaxHeight > 2 ? (BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2) : (DefaultAssistiveLabelPadding * 2 + BaseLineMaxHeight);
+ }
+
+ ///
+ /// This method updates the rectF of the floated hint text space in outlined type container.
+ ///
+ void CalculateClipRect()
+ {
+ if (!string.IsNullOrEmpty(Hint) && EnableFloating)
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ _clipRect.X = _outlineRectF.X + StartX + (float) GetAdditionalWidth();
+ }
+ else
+ {
+ _clipRect.X = (float)(_outlineRectF.X + StartX + (BaseLineMaxHeight / 2)) + (float)GetAdditionalWidth();
+ }
+
+ _clipRect.Y = 0;
+ _clipRect.Width = (float)Math.Min((FloatedHintSize.Width + DefaultAssistiveLabelPadding + DefaultAssistiveLabelPadding), GetHintTextWidth());
+ if (BaseLineMaxHeight <= 2)
+ {
+ _clipRect.Height = FloatedHintSize.Height;
+ }
+ else
+ {
+ _clipRect.Height = (float)((BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height);
+ }
+
+ if (ShowLeadingView && LeadingView != null && LeadingViewPosition == ViewPosition.Inside)
+ {
+ _clipRect.X = (float)(_clipRect.X + LeadingView.Width + _leadingViewLeftPadding);
+ }
+ }
+ else
+ {
+ _clipRect = new Rect(0, 0, 0, 0);
+ }
+ }
+
+ ///
+ /// This method updates the downIconRectF and clearIconRectF.
+ ///
+ void UpdateIconRectF()
+ {
+ UpdateOutlineRectF();
+ UpdateBackgroundRectF();
+ UpdateLeadViewWidthForContent();
+ UpdateTrailViewWidthForContent();
+ UpdateDownIconRectF();
+ UpdateUpIconRectF();
+ UpdateClearIconRectF();
+ UpdatePasswordToggleIconRectF();
+ UpdateEffectsRendererBounds();
+ }
+
+
+ ///
+ /// Configures the `_tilRenderer` field with a platform-specific `SfEntry` renderer:
+ /// - `MaterialSfEntryRenderer` for Android with padding.
+ /// - `FluentSfEntryRenderer` for Windows.
+ /// - `CupertinoSfEntryRenderer` for other platforms.
+ ///
+ void SetRendererBasedOnPlatform()
+ {
+#if ANDROID
+ _tilRenderer = new MaterialSfEntryRenderer() { _clearButtonPadding = 12};
+#elif WINDOWS
+ _tilRenderer = new FluentSfEntryRenderer();
+#else
+ _tilRenderer = new CupertinoSfEntryRenderer();
+#endif
+ }
+
+ ///
+ /// This method calculate the password toggle icon rectF position.
+ ///
+ void UpdatePasswordToggleIconRectF()
+ {
+ _passwordToggleIconRectF.X = (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
+ if (IsNone && Content != null)
+ {
+ _passwordToggleIconRectF.Y = (float)((Content.Y + (Content.Height / 2)) - (UpDownButtonSize / 2));
+ }
+ else if (IsOutlined)
+ {
+ _passwordToggleIconRectF.Y = ((_outlineRectF.Center.Y) - (UpDownButtonSize / 2));
+ }
+ else
+ {
+ _passwordToggleIconRectF.Y = (float)(((Height - AssistiveLabelPadding - TotalAssistiveTextHeight()) / 2) - (UpDownButtonSize / 2));
+ }
+
+ _passwordToggleIconRectF.Width = IsPassowordToggleIconVisible ? UpDownButtonSize : 0;
+ _passwordToggleIconRectF.Height = IsPassowordToggleIconVisible ? UpDownButtonSize : 0;
+
+ if (IsRTL)
+ {
+ _passwordToggleIconRectF.X = (float)(Width - _passwordToggleIconRectF.X - _passwordToggleIconRectF.Width);
+ }
+
+ }
+
+ ///
+ /// Updates the hint color of UI elements based on state.
+ ///
+ void UpdateHintColor()
+ {
+ if (!HintLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
+ {
+ _internalHintLabelStyle.TextColor = HintLabelStyle.TextColor;
+ }
+ else
+ {
+ if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ {
+ _internalHintLabelStyle.TextColor = IsEnabled ? ((IsLayoutFocused || HasError) && Stroke != null) ? Stroke : HintLabelStyle.TextColor : DisabledColor;
+ }
+ else
+ {
+ if (IsEnabled)
+ {
+ if (IsLayoutFocused)
+ {
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutFocusedHintTextColor");
+ _internalHintLabelStyle.TextColor = HintTextColor;
+ }
+ else
+ {
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutHintTextColor");
+ _internalHintLabelStyle.TextColor = HintTextColor;
+ }
+
+ }
+ else
+ {
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutDisabledHintTextColor");
+ _internalHintLabelStyle.TextColor = HintTextColor;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Updates the helper color of UI elements based on state.
+ ///
+ void UpdateHelperTextColor()
+ {
+ if (!HelperLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
+ {
+ _internalHelperLabelStyle.TextColor = HelperLabelStyle.TextColor;
+ }
+ else
+ {
+ if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ {
+ _internalHelperLabelStyle.TextColor = IsEnabled ? ((HasError) && Stroke != null) ? Stroke : HelperLabelStyle.TextColor : DisabledColor;
+ }
+ else
+ {
+ if (IsEnabled)
+ {
+ SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutHelperTextColor");
+ _internalHelperLabelStyle.TextColor = HelperTextColor;
+ }
+ else
+ {
+ SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutDisabledHelperTextColor");
+ _internalHelperLabelStyle.TextColor = HelperTextColor;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Updates the error color of UI elements based on state.
+ ///
+ void UpdateErrorTextColor()
+ {
+ _internalErrorLabelStyle.TextColor = IsEnabled ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : DisabledColor;
+ }
+
+ ///
+ /// Updates the counter color of UI elements based on state.
+ ///
+ void UpdateCounterTextColor()
+ {
+ _internalCounterLabelStyle.TextColor = IsEnabled ? HasError ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : CounterLabelStyle.TextColor : DisabledColor;
+ }
+
+ ///
+ /// Check the stroke value is set or not.
+ ///
+ bool HasStrokeValue()
+ {
+ return Stroke is not null && Stroke.Equals(_defaultStrokeColor);
+ }
+
+ ///
+ /// Check the container background value is set or not.
+ ///
+ bool HasContainerBackgroundValue()
+ {
+ return ContainerBackground is not null && ContainerBackground.Equals(_defaultContainerBackground);
+ }
+
+ void DrawBorder(ICanvas canvas, RectF dirtyRect)
+ {
+ canvas.CanvasSaveState();
+ if (IsRTL)
+ {
+ canvas.Translate((float)Width, 0);
+ canvas.Scale(-1, 1);
+ }
+ canvas.StrokeSize = (float)(IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness);
+
+ SetStrokeColor(canvas);
+
+ if (!IsOutlined)
+ {
+ DrawFilledOrNoneBorder(canvas);
+ }
+ else
+ {
+ DrawOutlinedBorder(canvas);
+ }
+
+ canvas.CanvasRestoreState();
+ }
+
+ void SetStrokeColor(ICanvas canvas)
+ {
+ if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _) && !VisualStateManager.HasVisualStateGroups(this))
+ {
+ canvas.StrokeColor = IsEnabled ? Stroke : DisabledColor;
+ }
+ else
+ {
+ canvas.StrokeColor = Stroke;
+ }
+ }
+
+ void DrawFilledOrNoneBorder(ICanvas canvas)
+ {
+ UpdateBaseLinePoints();
+ if (IsFilled)
+ {
+ canvas.CanvasSaveState();
+ if (ContainerBackground is SolidColorBrush backgroundColor)
+ {
+ canvas.FillColor = backgroundColor.Color;
+ }
+
+ UpdateBackgroundRectF();
+ canvas.FillRoundedRectangle(_backgroundRectF, OutlineCornerRadius, OutlineCornerRadius, 0, 0);
+ canvas.CanvasRestoreState();
+ }
+
+ if ((IsLayoutFocused && FocusedStrokeThickness > 0) || (!IsLayoutFocused && UnfocusedStrokeThickness > 0))
+ {
+ canvas.DrawLine(_startPoint.X, _startPoint.Y, _endPoint.X, _endPoint.Y);
+ }
+ }
+
+ ///
+ /// Draws the clear button path on the canvas.
+ ///
+ /// The canvas to draw on.
+ /// The path of the clear button to draw.
+ void DrawClearButtonPath(ICanvas canvas, Microsoft.Maui.Controls.Shapes.Path clearButtonPath)
+ {
+ PathF path = clearButtonPath.GetPath();
+ canvas.FillColor = clearButtonPath.Fill is SolidColorBrush solidColorBrushFill ? solidColorBrushFill.Color : Colors.Transparent;
+ canvas.StrokeColor = clearButtonPath.Stroke is SolidColorBrush solidColorBrushStroke ? solidColorBrushStroke.Color : ClearIconStrokeColor;
+ canvas.ClipRectangle(UpdatePathRect());
+ canvas.Translate(_clearIconRectF.Center.X - path.Bounds.Center.X, _clearIconRectF.Center.Y - path.Bounds.Center.Y);
+ canvas.FillPath(path, WindingMode.EvenOdd);
+ canvas.DrawPath(path);
+ }
+
+ ///
+ /// Draws the clear icon on the canvas if it's visible.
+ ///
+ /// The canvas to draw on.
+ /// The rectangle that needs to be redrawn.
+ void DrawClearIcon(ICanvas canvas, RectF dirtyRect)
+ {
+ if (IsClearIconVisible)
+ {
+ canvas.SaveState();
+ if (ClearButtonPath != null)
+ {
+ DrawClearButtonPath(canvas, ClearButtonPath);
+ }
+ else
+ {
+ canvas.StrokeColor = ClearButtonColor;
+ canvas.StrokeSize = 1.5f;
+ _tilRenderer?.DrawClearButton(canvas, _clearIconRectF);
+ }
+ canvas.RestoreState();
+ }
+ }
+
+ void DrawOutlinedBorder(ICanvas canvas)
+ {
+ UpdateOutlineRectF();
+ if (BaseLineMaxHeight > 2)
+ {
+ UpdateOutlineBackgroundRectF();
+ }
+
+ if (((IsLayoutFocused && !string.IsNullOrEmpty(Hint)) || IsHintFloated) && ShowHint)
+ {
+ CalculateClipRect();
+ canvas.SubtractFromClip(_clipRect);
+ }
+
+ SetOutlinedContainerBackground(canvas);
+
+ if (OutlineCornerRadius != 0)
+ {
+ DrawRoundedOutlinedBorder(canvas);
+ }
+ else
+ {
+ canvas.FillRectangle(_backgroundRectF);
+ if ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) > 0)
+ {
+ canvas.DrawRectangle(_outlineRectF);
+ }
+ }
+ }
+
+ void SetOutlinedContainerBackground(ICanvas canvas)
+ {
+ if (Application.Current != null && Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ {
+ SetDynamicResource(OutlinedContainerBackgroundProperty, "SfTextInputLayoutOutlinedContainerBackground");
+ if (OutlinedContainerBackground is SolidColorBrush backgroundColor)
+ {
+ canvas.FillColor = backgroundColor.Color;
+ }
+ }
+ else
+ {
+ if (ContainerBackground is SolidColorBrush backgroundColor)
+ {
+ canvas.FillColor = backgroundColor.Color;
+ }
+ }
+ }
+
+ void DrawRoundedOutlinedBorder(ICanvas canvas)
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ canvas.FillRoundedRectangle(_outlineRectF, OutlineCornerRadius);
+ }
+ else
+ {
+ var cornerRadius = ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) / 2) < OutlineCornerRadius ? OutlineCornerRadius - ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) / 2) : 0;
+ canvas.FillRoundedRectangle(_backgroundRectF, cornerRadius);
+ }
+ if ((IsLayoutFocused ? FocusedStrokeThickness : UnfocusedStrokeThickness) > 0)
+ {
+ canvas.DrawRoundedRectangle(_outlineRectF, OutlineCornerRadius);
+ }
+ }
+
+ void DrawHintText(ICanvas canvas, RectF dirtyRect)
+ {
+ if ((IsHintFloated && !EnableFloating) || (!IsHintFloated && !string.IsNullOrEmpty(Text)))
+ {
+ return;
+ }
+
+ if (ShowHint && !string.IsNullOrEmpty(Hint) && HintLabelStyle != null)
+ {
+ canvas.CanvasSaveState();
+ UpdateOutlineRectF();
+ UpdateHintPosition();
+ UpdateHintColor();
+
+ _internalHintLabelStyle.FontSize = _isAnimating ? (float)_animatingFontSize : IsHintFloated ? FloatedHintFontSize : HintLabelStyle.FontSize;
+
+ HorizontalAlignment horizontalAlignment = IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left;
+#if IOS || MACCATALYST
+ VerticalAlignment verticalAlignment = VerticalAlignment.Top;
+#else
+ VerticalAlignment verticalAlignment = VerticalAlignment.Center;
+#endif
+
+ canvas.DrawText(Hint, _hintRect, horizontalAlignment, verticalAlignment, _internalHintLabelStyle);
+ canvas.CanvasRestoreState();
+ }
+ }
+
+ void DrawAssistiveText(ICanvas canvas, RectF dirtyRect)
+ {
+ if (HasError)
+ {
+ DrawErrorText(canvas, dirtyRect);
+ }
+ else
+ {
+ DrawHelperText(canvas, dirtyRect);
+ }
+
+ DrawCounterText(canvas, dirtyRect);
+ }
+
+ void DrawHelperText(ICanvas canvas, RectF dirtyRect)
+ {
+ if (ShowHelperText && !string.IsNullOrEmpty(HelperText) && HelperLabelStyle != null)
+ {
+ canvas.CanvasSaveState();
+ UpdateHelperTextPosition();
+ UpdateHelperTextColor();
+
+ canvas.DrawText(HelperText, _helperTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalHelperLabelStyle);
+
+ canvas.CanvasRestoreState();
+ }
+ }
+
+ void DrawErrorText(ICanvas canvas, RectF dirtyRect)
+ {
+ if (!string.IsNullOrEmpty(ErrorText) && ErrorLabelStyle != null)
+ {
+ canvas.CanvasSaveState();
+ UpdateErrorTextPosition();
+ UpdateErrorTextColor();
+
+ canvas.DrawText(ErrorText, _errorTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalErrorLabelStyle);
+
+ canvas.CanvasRestoreState();
+ }
+ }
+
+ void DrawCounterText(ICanvas canvas, RectF dirtyRect)
+ {
+ if (ShowCharCount && !string.IsNullOrEmpty(_counterText) && CounterLabelStyle != null)
+ {
+ canvas.CanvasSaveState();
+ UpdateCounterTextPosition();
+ UpdateCounterTextColor();
+
+ canvas.DrawText(_counterText, _counterTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalCounterLabelStyle);
+
+ canvas.CanvasRestoreState();
+ }
+ }
+
+ ///
+ /// Updates the clipping bound of the clear button path.
+ ///
+ /// The RectF
+ RectF UpdatePathRect()
+ {
+ RectF clipSize = new()
+ {
+ Width = _clearIconRectF.Width / 2,
+ Height = _clearIconRectF.Height / 2,
+ };
+ clipSize.X = _clearIconRectF.Center.X - clipSize.Width / 2;
+ clipSize.Y = _clearIconRectF.Center.Y - clipSize.Height / 2;
+
+ return clipSize;
+ }
+
+ void DrawPasswordToggleIcon(ICanvas canvas, RectF dirtyRect)
+ {
+ if (IsPassowordToggleIconVisible)
+ {
+ canvas.CanvasSaveState();
+
+ if (!Application.Current!.Resources.TryGetValue("SfTextInputLayoutTheme", out _) && !VisualStateManager.HasVisualStateGroups(this))
+ {
+ canvas.FillColor = IsEnabled ? Stroke : DisabledColor;
+ }
+ else
+ {
+ canvas.FillColor = Stroke;
+ }
+
+ canvas.Translate(_passwordToggleIconRectF.X + _passwordToggleIconEdgePadding, _passwordToggleIconRectF.Y + (_isPasswordTextVisible ? _passwordToggleVisibleTopPadding : _passwordToggleCollapsedTopPadding));
+
+ // Using Path data value for toggle icon it seems smaller in Android platform when compared to Xamarin.Forms UI
+ // so fix the above issue by scaling the canvas.
+ if (DeviceInfo.Platform == DevicePlatform.Android)
+ {
+ canvas.Scale(1.2f, 1.2f);
+ }
+
+ canvas.FillPath(ToggleIconPath);
+ canvas.CanvasRestoreState();
+ }
+ }
+
+ void UpdateSizeStartAndEndValue()
+ {
+ _fontSizeStart = IsHintFloated ? FloatedHintFontSize : HintFontSize;
+ _fontSizeEnd = IsHintFloated ? HintFontSize : FloatedHintFontSize;
+ }
+
+ void UpdateStartAndEndValue()
+ {
+ if (IsNone && (Width > 0 && Height > 0))
+ {
+ if (IsHintFloated)
+ {
+ _translateStart = DefaultAssistiveLabelPadding;
+ _translateEnd = Height - HintSize.Height - BaseLineMaxHeight - (DefaultAssistiveLabelPadding * 2) - AssistiveLabelPadding - TotalAssistiveTextHeight() - DefaultAssistiveLabelPadding;
+ }
+ else
+ {
+ _translateStart = Height - HintSize.Height - BaseLineMaxHeight - (DefaultAssistiveLabelPadding * 2) - AssistiveLabelPadding - TotalAssistiveTextHeight() - DefaultAssistiveLabelPadding;
+ _translateEnd = DefaultAssistiveLabelPadding;
+ }
+ }
+
+ if (IsOutlined && (Width > 0 && Height > 0))
+ {
+ if (IsHintFloated)
+ {
+ _translateStart = 0;
+ _translateEnd = _outlineRectF.Center.Y - (HintSize.Height / 2);
+ }
+ else
+ {
+ _translateStart = _outlineRectF.Center.Y - (HintSize.Height / 2);
+ _translateEnd = 0;
+ }
+ }
+
+ if (IsFilled && (Width > 0 && Height > 0))
+ {
+ if (IsHintFloated)
+ {
+ _translateStart = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
+ _translateEnd = ((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
+ }
+ else
+ {
+ _translateStart = ((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
+ _translateEnd = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
+ }
+ }
+ }
+
+ void OnScalingAnimationUpdate(double value)
+ {
+ _fontsize = (float)value;
+ }
+
+ void OnTranslateAnimationEnded(double value, bool isCompleted)
+ {
+ _isAnimating = false;
+ IsHintDownToUp = !IsHintDownToUp;
+ }
+
+ ///
+ /// Returns the resource dictionary for the current theme of the parent element.
+ ///
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfTextInputLayoutStyles();
+ }
+
+ ///
+ /// Handles changes in the theme for individual controls.
+ ///
+ ///
+ ///
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+
+ }
+
+ ///
+ /// Handles changes in the common theme shared across multiple elements.
+ ///
+ ///
+ ///
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+
+ }
+
+ #endregion
- canvas.DrawText(Hint, _hintRect, horizontalAlignment, verticalAlignment, _internalHintLabelStyle);
- canvas.CanvasRestoreState();
- }
- }
-
- void DrawAssistiveText(ICanvas canvas, RectF dirtyRect)
- {
- if (HasError)
- {
- DrawErrorText(canvas, dirtyRect);
- }
- else
- {
- DrawHelperText(canvas, dirtyRect);
- }
-
- DrawCounterText(canvas, dirtyRect);
- }
-
- void DrawHelperText(ICanvas canvas, RectF dirtyRect)
- {
- if (ShowHelperText && !string.IsNullOrEmpty(HelperText) && HelperLabelStyle != null)
- {
- canvas.CanvasSaveState();
- UpdateHelperTextPosition();
- UpdateHelperTextColor();
-
- canvas.DrawText(HelperText, _helperTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalHelperLabelStyle);
-
- canvas.CanvasRestoreState();
- }
- }
-
- void DrawErrorText(ICanvas canvas, RectF dirtyRect)
- {
- if (!string.IsNullOrEmpty(ErrorText) && ErrorLabelStyle != null)
- {
- canvas.CanvasSaveState();
- UpdateErrorTextPosition();
- UpdateErrorTextColor();
-
- canvas.DrawText(ErrorText, _errorTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalErrorLabelStyle);
-
- canvas.CanvasRestoreState();
- }
- }
-
- void DrawCounterText(ICanvas canvas, RectF dirtyRect)
- {
- if (ShowCharCount && !string.IsNullOrEmpty(_counterText) && CounterLabelStyle != null)
- {
- canvas.CanvasSaveState();
- UpdateCounterTextPosition();
- UpdateCounterTextColor();
-
- canvas.DrawText(_counterText, _counterTextRect, IsRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left, VerticalAlignment.Top, _internalCounterLabelStyle);
-
- canvas.CanvasRestoreState();
- }
- }
-
- ///
- /// Updates the clipping bound of the clear button path.
- ///
- /// The RectF
- RectF UpdatePathRect()
- {
- RectF clipSize = new()
- {
- Width = _clearIconRectF.Width / 2,
- Height = _clearIconRectF.Height / 2,
- };
- clipSize.X = _clearIconRectF.Center.X - clipSize.Width / 2;
- clipSize.Y = _clearIconRectF.Center.Y - clipSize.Height / 2;
-
- return clipSize;
- }
-
- void DrawPasswordToggleIcon(ICanvas canvas, RectF dirtyRect)
- {
- if (IsPassowordToggleIconVisible)
- {
- canvas.CanvasSaveState();
-
- if (!Application.Current!.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme) && !VisualStateManager.HasVisualStateGroups(this))
- {
- canvas.FillColor = IsEnabled ? Stroke : DisabledColor;
- }
- else
- {
- canvas.FillColor = Stroke;
- }
-
- canvas.Translate(_passwordToggleIconRectF.X + _passwordToggleIconEdgePadding, _passwordToggleIconRectF.Y + (_isPasswordTextVisible ? _passwordToggleVisibleTopPadding : _passwordToggleCollapsedTopPadding));
-
- // Using Path data value for toggle icon it seems smaller in Android platform when compared to Xamarin.Forms UI
- // so fix the above issue by scaling the canvas.
- if (DeviceInfo.Platform == DevicePlatform.Android)
- {
- canvas.Scale(1.2f, 1.2f);
- }
-
- canvas.FillPath(ToggleIconPath);
- canvas.CanvasRestoreState();
- }
- }
-
- void UpdateSizeStartAndEndValue()
- {
- _fontSizeStart = IsHintFloated ? FloatedHintFontSize : HintFontSize;
- _fontSizeEnd = IsHintFloated ? HintFontSize : FloatedHintFontSize;
- }
-
- void UpdateStartAndEndValue()
- {
- if (IsNone && (Width > 0 && Height > 0))
- {
- if (IsHintFloated)
- {
- _translateStart = DefaultAssistiveLabelPadding;
- _translateEnd = Height - HintSize.Height - BaseLineMaxHeight - (DefaultAssistiveLabelPadding * 2) - AssistiveLabelPadding - TotalAssistiveTextHeight() - DefaultAssistiveLabelPadding;
- }
- else
- {
- _translateStart = Height - HintSize.Height - BaseLineMaxHeight - (DefaultAssistiveLabelPadding * 2) - AssistiveLabelPadding - TotalAssistiveTextHeight() - DefaultAssistiveLabelPadding;
- _translateEnd = DefaultAssistiveLabelPadding;
- }
- }
-
- if (IsOutlined && (Width > 0 && Height > 0))
- {
- if (IsHintFloated)
- {
- _translateStart = 0;
- _translateEnd = _outlineRectF.Center.Y - (HintSize.Height / 2);
- }
- else
- {
- _translateStart = _outlineRectF.Center.Y - (HintSize.Height / 2);
- _translateEnd = 0;
- }
- }
-
- if (IsFilled && (Width > 0 && Height > 0))
- {
- if (IsHintFloated)
- {
- _translateStart = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
- _translateEnd = ((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
- }
- else
- {
- _translateStart = ((Height - TotalAssistiveTextHeight() - AssistiveLabelPadding) / 2) - (HintSize.Height / 2);
- _translateEnd = (float)((DefaultAssistiveLabelPadding * 2) + (DefaultAssistiveLabelPadding / 2));
- }
- }
- }
-
- void OnScalingAnimationUpdate(double value)
- {
- _fontsize = (float)value;
- }
-
- void OnTranslateAnimationUpdate(double value)
- {
- _isAnimating = true;
- _hintRect.Y = (float)value;
- _animatingFontSize = _fontsize;
- InvalidateDrawable();
- }
-
- void OnTranslateAnimationEnded(double value, bool isCompleted)
- {
- _isAnimating = false;
- IsHintDownToUp = !IsHintDownToUp;
- }
-
- ///
- /// Returns the resource dictionary for the current theme of the parent element.
- ///
- ResourceDictionary IParentThemeElement.GetThemeDictionary()
- {
- return new SfTextInputLayoutStyles();
- }
-
- ///
- /// Handles changes in the theme for individual controls.
- ///
- ///
- ///
- void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
- {
-
- }
-
- ///
- /// Handles changes in the common theme shared across multiple elements.
- ///
- ///
- ///
- void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
- {
-
- }
-
- #endregion
-
- }
+ }
}
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs b/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
index 55bc6e05..b2b5f483 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
@@ -3,2179 +3,2350 @@
namespace Syncfusion.Maui.Toolkit.TextInputLayout
{
- public partial class SfTextInputLayout
- {
- #region Bindable Properties
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property indicates whether the layout has focus or not in the .
- ///
- static readonly BindableProperty IsLayoutFocusedProperty =
- BindableProperty.Create(
- nameof(IsLayoutFocused),
- typeof(bool),
- typeof(SfTextInputLayout),
- false,
- BindingMode.Default,
- null,
- OnIsLayoutFocusedChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the appearance of the background and border in the .
- ///
- public static readonly BindableProperty ContainerTypeProperty =
- BindableProperty.Create(
- nameof(ContainerType),
- typeof(ContainerType),
- typeof(SfTextInputLayout),
- ContainerType.Filled,
- BindingMode.Default,
- null,
- OnContainerTypePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property specifies the display of icons or other visual elements in the leading view of .
- ///
- public static readonly BindableProperty LeadingViewProperty =
- BindableProperty.Create(
- nameof(LeadingView),
- typeof(View),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- OnLeadingViewChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property specifies the display of icons or other visual elements in the trailing view of .
- ///
- public static readonly BindableProperty TrailingViewProperty =
- BindableProperty.Create(
- nameof(TrailingView),
- typeof(View),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- OnTrailingViewChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the leading view should be displayed in the .
- ///
- public static readonly BindableProperty ShowLeadingViewProperty =
- BindableProperty.Create(
- nameof(ShowLeadingView),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnShowLeadingViewPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the trailing view should be displayed in the .
- ///
- public static readonly BindableProperty ShowTrailingViewProperty =
- BindableProperty.Create(
- nameof(ShowTrailingView),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnShowTrailingViewPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the hint text should be displayed or not in the .
- ///
- public static readonly BindableProperty ShowHintProperty =
- BindableProperty.Create(
- nameof(ShowHint),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the character count should be displayed or not in the .
- ///
- internal static readonly BindableProperty ShowCharCountProperty =
- BindableProperty.Create(
- nameof(ShowCharCount),
- typeof(bool),
- typeof(SfTextInputLayout),
- false, BindingMode.Default,
- null, OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the helper text should be displayed or not in the .
- ///
- public static readonly BindableProperty ShowHelperTextProperty =
- BindableProperty.Create(
- nameof(ShowHelperText),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.TwoWay,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the error text should be displayed or not in the .
- ///
- public static readonly BindableProperty HasErrorProperty =
- BindableProperty.Create(
- nameof(HasError),
- typeof(bool),
- typeof(SfTextInputLayout),
- false,
- BindingMode.Default,
- null,
- OnHasErrorPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the hint text should always float or not in the .
- ///
- public static readonly BindableProperty IsHintAlwaysFloatedProperty =
- BindableProperty.Create(
- nameof(IsHintAlwaysFloated),
- typeof(bool),
- typeof(SfTextInputLayout),
- false,
- BindingMode.Default,
- null,
- OnHintAlwaysFloatedPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines border color of the .
- ///
- public static readonly BindableProperty StrokeProperty =
- BindableProperty.Create(
- nameof(Stroke),
- typeof(Color),
- typeof(SfTextInputLayout),
- _defaultStrokeColor,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines background color of the container in the .
- ///
- public static readonly BindableProperty ContainerBackgroundProperty =
- BindableProperty.Create(
- nameof(ContainerBackground),
- typeof(Brush),
- typeof(SfTextInputLayout),
- _defaultContainerBackground,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property specifies the corner radius of the outline border in the .
- ///
- public static readonly BindableProperty OutlineCornerRadiusProperty =
- BindableProperty.Create(
- nameof(OutlineCornerRadius),
- typeof(double),
- typeof(SfTextInputLayout),
- 3.5d,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the stroke thickness of the border when the is focused.
- ///
- public static readonly BindableProperty FocusedStrokeThicknessProperty =
- BindableProperty.Create(
- nameof(FocusedStrokeThickness),
- typeof(double),
- typeof(SfTextInputLayout),
- 2d,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines stroke thickness of the border when the is unfocused.
- ///
- public static readonly BindableProperty UnfocusedStrokeThicknessProperty =
- BindableProperty.Create(
- nameof(UnfocusedStrokeThickness),
- typeof(double),
- typeof(SfTextInputLayout),
- 1d,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines maximum character length of the .
- ///
- public static readonly BindableProperty CharMaxLengthProperty =
- BindableProperty.Create(
- nameof(CharMaxLength),
- typeof(int),
- typeof(SfTextInputLayout),
- int.MaxValue,
- BindingMode.Default,
- null,
- OnCharMaxLengthPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines hint text in the .
- ///
- public static readonly BindableProperty HintProperty =
- BindableProperty.Create(
- nameof(Hint),
- typeof(string),
- typeof(SfTextInputLayout),
- string.Empty,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines helper text of the .
- ///
- public static readonly BindableProperty HelperTextProperty =
- BindableProperty.Create(
- nameof(HelperText),
- typeof(string),
- typeof(SfTextInputLayout),
- string.Empty,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines error text of the .
- ///
- public static readonly BindableProperty ErrorTextProperty =
- BindableProperty.Create(
- nameof(ErrorText),
- typeof(string),
- typeof(SfTextInputLayout),
- string.Empty,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the reserved space for assistive labels such as helper text, error text, and character counters in the .
- ///
- public static readonly BindableProperty ReserveSpaceForAssistiveLabelsProperty =
- BindableProperty.Create(
- nameof(ReserveSpaceForAssistiveLabels),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnReserveSpacePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines leading view position in the .
- ///
- public static readonly BindableProperty LeadingViewPositionProperty =
- BindableProperty.Create(
- nameof(LeadingViewPosition),
- typeof(ViewPosition),
- typeof(SfTextInputLayout),
- ViewPosition.Inside,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines trailing view position in the .
- ///
- public static readonly BindableProperty TrailingViewPositionProperty =
- BindableProperty.Create(
- nameof(TrailingViewPosition),
- typeof(ViewPosition),
- typeof(SfTextInputLayout),
- ViewPosition.Inside,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines padding for the input view in the .
- ///
- public static readonly BindableProperty InputViewPaddingProperty =
- BindableProperty.Create(
- nameof(InputViewPadding),
- typeof(Thickness),
- typeof(SfTextInputLayout),
- new Thickness(-1, -1, -1, -1),
- BindingMode.Default,
- null,
- OnInputViewPaddingPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether need to display the password visibility toggle in the .
- ///
- public static readonly BindableProperty EnablePasswordVisibilityToggleProperty =
- BindableProperty.Create(
- nameof(EnablePasswordVisibilityToggle),
- typeof(bool),
- typeof(SfTextInputLayout),
- false,
- BindingMode.Default,
- null,
- OnEnablePasswordTogglePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the Label should float when the input is focused or unfocused in the .
- ///
- public static readonly BindableProperty EnableFloatingProperty =
- BindableProperty.Create(
- nameof(EnableFloating),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether need to enable the animation for hint text when input view is focused or unfocused in .
- ///
- public static readonly BindableProperty EnableHintAnimationProperty =
- BindableProperty.Create(
- nameof(EnableHintAnimation),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines color of the clear button in the .
- ///
- internal static readonly BindableProperty ClearButtonColorProperty =
- BindableProperty.Create(
- nameof(ClearButtonColor),
- typeof(Color),
- typeof(SfTextInputLayout),
- ClearIconStrokeColor,
- BindingMode.Default,
- null,
- OnPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the Label style of the hint text in the .
- ///
- public static readonly BindableProperty HintLabelStyleProperty =
- BindableProperty.Create(
- nameof(HintLabelStyle),
- typeof(LabelStyle),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- defaultValueCreator: bindale => GetHintLabelStyleDefaultValue(),
- propertyChanged: OnHintLableStylePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the Label style of the helper text in the .
- ///
- public static readonly BindableProperty HelperLabelStyleProperty =
- BindableProperty.Create(
- nameof(HelperLabelStyle),
- typeof(LabelStyle),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- defaultValueCreator: bindale => GetHelperLabelStyleDefaultValue(),
- propertyChanged: OnHelperLableStylePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the Label style of the error text in the .
- ///
- public static readonly BindableProperty ErrorLabelStyleProperty =
- BindableProperty.Create(
- nameof(ErrorLabelStyle),
- typeof(LabelStyle),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- defaultValueCreator: bindale => GetErrorLabelStyleDefaultValue(),
- propertyChanged: OnErrorLableStylePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the Label style of the counter text in the .
- ///
- internal static readonly BindableProperty CounterLabelStyleProperty =
- BindableProperty.Create(
- nameof(CounterLabelStyle),
- typeof(LabelStyle),
- typeof(SfTextInputLayout),
- null,
- BindingMode.Default,
- null,
- defaultValueCreator: bindale => GetCounterLabelStyleDefaultValue(),
- propertyChanged: OnCounterLableStylePropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the current active color of the .
- ///
- internal static readonly BindablePropertyKey CurrentActiveColorKey =
- BindableProperty.CreateReadOnly(
- nameof(CurrentActiveColor),
- typeof(Color),
- typeof(SfTextInputLayout),
- Color.FromRgba("#79747E"),
- BindingMode.Default,
- null,
- null);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the hint text is floated or not in the .
- ///
- internal static readonly BindableProperty IsHintFloatedProperty =
- BindableProperty.Create(
- nameof(IsHintFloated),
- typeof(bool),
- typeof(SfTextInputLayout),
- false,
- BindingMode.Default,
- null,
- OnIsHintFloatedPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines current active color of the .
- ///
- public static readonly BindableProperty CurrentActiveColorProperty = CurrentActiveColorKey.BindableProperty;
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the control is enabled or not in the .
- ///
- public static new readonly BindableProperty IsEnabledProperty =
- BindableProperty.Create(
- nameof(IsEnabled),
- typeof(bool),
- typeof(SfTextInputLayout),
- true,
- BindingMode.Default,
- null,
- OnEnabledPropertyChanged);
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the color of the hint text in .
- ///
- internal static readonly BindableProperty HintTextColorProperty =
- BindableProperty.Create(
- nameof(HintTextColor),
- typeof(Color),
- typeof(LabelStyle),
- Color.FromArgb("#49454F"));
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the color of the helper text in .
- ///
- internal static readonly BindableProperty HelperTextColorProperty =
- BindableProperty.Create(
- nameof(HelperTextColor),
- typeof(Color),
- typeof(LabelStyle),
- Color.FromArgb("#49454F"));
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the background color of the outline container in .
- ///
- internal static readonly BindableProperty OutlinedContainerBackgroundProperty =
- BindableProperty.Create(
- nameof(OutlinedContainerBackground),
- typeof(Brush),
- typeof(SfTextInputLayout),
- new SolidColorBrush(Colors.Transparent));
-
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property allows for the customization of the appearance of clear button in the .
- ///
- internal static readonly BindableProperty ClearButtonPathProperty =
- BindableProperty.Create(
- nameof(ClearButtonPath),
- typeof(Path),
- typeof(SfTextInputLayout),
- null,
- BindingMode.OneWay,
- null,
- OnClearButtonPathChanged);
-
- ///
- /// Returns the default label style for hint label.
- ///
- /// The LabelStyle
- static LabelStyle GetHintLabelStyleDefaultValue()
- {
- return new LabelStyle() { FontSize = DefaultHintFontSize };
- }
-
- ///
- /// Returns the default label style for helper label.
- ///
- /// The LabelStyle
- static LabelStyle GetHelperLabelStyleDefaultValue()
- {
- return new LabelStyle();
- }
-
- ///
- /// Returns the default label style for Error label.
- ///
- /// The LabelStyle
- static LabelStyle GetErrorLabelStyleDefaultValue()
- {
- return new LabelStyle();
- }
-
- ///
- /// Returns the default label style for counter label.
- ///
- /// The LabelStyle
- static LabelStyle GetCounterLabelStyleDefaultValue()
- {
- return new LabelStyle();
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets or sets a value indicating whether the hint text should be displayed.
- ///
- /// true if the hint text should be displayed; otherwise, false.
- ///
- /// The following code demonstrates how to use the ShowHint property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool ShowHint
- {
- get { return (bool)GetValue(ShowHintProperty); }
- set { SetValue(ShowHintProperty, value); }
- }
-
- ///
- /// Gets or sets a value indicating whether to display the character count when the value is changed.
- ///
- /// true if enable counter; otherwise, false.
- internal bool ShowCharCount
- {
- get { return (bool)GetValue(ShowCharCountProperty); }
- set { SetValue(ShowCharCountProperty, value); }
- }
-
- ///
- /// Gets or sets a value indicating whether the helper text should be displayed.
- ///
- /// true if the helper text should be displayed; otherwise, false.
- ///
- /// The following code demonstrates how to use the ShowHelperText property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool ShowHelperText
- {
- get => (bool)GetValue(ShowHelperTextProperty);
- set => SetValue(ShowHelperTextProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether the input has validation errors.
- ///
- /// true if there are validation errors; otherwise, false.
- ///
- /// The following code demonstrates how to use the HasError property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool HasError
- {
- get => (bool)GetValue(HasErrorProperty);
- set => SetValue(HasErrorProperty, value);
- }
-
- ///
- /// Gets or sets the color of the border or base line, depending on the container type.
- ///
- ///
- /// The following code demonstrates how to use the Stroke property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public Color Stroke
- {
- get => (Color)GetValue(StrokeProperty);
- set => SetValue(StrokeProperty, value);
- }
-
- ///
- /// Gets or sets the Hint text font size.
- ///
- internal float HintFontSize { get; set; }
-
- ///
- /// Gets or sets the background of the container.
- ///
- ///
- /// The following code demonstrates how to use the ContainerBackground property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public Brush ContainerBackground
- {
- get => (Brush)GetValue(ContainerBackgroundProperty);
- set => SetValue(ContainerBackgroundProperty, value);
- }
-
- ///
- /// Gets or sets the corner radius of the outline border.
- ///
- /// It is applicable only for the outlined container type.
- /// The default value is 4.
- ///
- /// The following code demonstrates how to use the OutlineCornerRadius property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public double OutlineCornerRadius
- {
- get => (double)GetValue(OutlineCornerRadiusProperty);
- set => SetValue(OutlineCornerRadiusProperty, value);
- }
-
- ///
- /// Gets or sets the stroke thickness of the bottom line or outline border when control is in a focused state.
- /// This property is applicable for both filled and outlined container types.
- ///
- /// The default value is 2.
- ///
- /// The following code demonstrates how to use the FocusedStrokeThickness property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public double FocusedStrokeThickness
- {
- get => (double)GetValue(FocusedStrokeThicknessProperty);
- set => SetValue(FocusedStrokeThicknessProperty, value);
- }
-
- ///
- /// Gets or sets the stroke thickness for the bottom line or outline border when control is in an unfocused state
- /// This property is applicable for filled and outlined container types.
- ///
- /// The default value is 1.
- ///
- /// The following code demonstrates how to use the UnfocusedStrokeThickness property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public double UnfocusedStrokeThickness
- {
- get => (double)GetValue(UnfocusedStrokeThicknessProperty);
- set => SetValue(UnfocusedStrokeThicknessProperty, value);
- }
-
- ///
- /// Gets or sets the maximum character length for the input. An error color is applied when this limit is exceeded.
- ///
- ///
- /// The following code demonstrates how to use the UnfocusedStrokeThickness property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public int CharMaxLength
- {
- get => (int)GetValue(CharMaxLengthProperty);
- set => SetValue(CharMaxLengthProperty, value);
- }
-
- ///
- /// Gets or sets the view to be displayed before the input view.
- ///
- /// This view is typically used to display icons or other visual elements.
- ///
- /// The following code demonstrates how to use the LeadingView property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public View LeadingView
- {
- get => (View)GetValue(LeadingViewProperty);
- set => SetValue(LeadingViewProperty, value);
- }
-
- ///
- /// Gets or sets the view to be displayed after the input view.
- ///
- /// This view is typically used to display icons or other visual elements.
- ///
- /// The following code demonstrates how to use the TrailingView property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public View TrailingView
- {
- get => (View)GetValue(TrailingViewProperty);
- set => SetValue(TrailingViewProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether the leading view should be displayed.
- /// The default value is true.
- ///
- /// true if the leading view should be displayed; otherwise, false.
- ///
- /// The following code demonstrates how to use the ShowLeadingView property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool ShowLeadingView
- {
- get => (bool)GetValue(ShowLeadingViewProperty);
- set => SetValue(ShowLeadingViewProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether the trailing view should be displayed.
- /// The default value is true.
- ///
- /// true if the trailing view should be displayed; otherwise, false.
- ///
- /// The following code demonstrates how to use the ShowTrailingView property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool ShowTrailingView
- {
- get => (bool)GetValue(ShowTrailingViewProperty);
- set => SetValue(ShowTrailingViewProperty, value);
- }
-
- ///
- /// Gets or sets the position of the leading view relative to the input layout.
- /// The default value is ViewPosition.Inside.
- ///
- ///
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public ViewPosition LeadingViewPosition
- {
- get => (ViewPosition)GetValue(LeadingViewPositionProperty);
- set => SetValue(LeadingViewPositionProperty, value);
- }
-
- ///
- /// Gets or sets the position of the trailing view relative to the input layout.
- /// The default value is ViewPosition.Inside.
- ///
- ///
- /// The following code demonstrates how to use the TrailingViewPosition property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public ViewPosition TrailingViewPosition
- {
- get => (ViewPosition)GetValue(TrailingViewPositionProperty);
- set => SetValue(TrailingViewPositionProperty, value);
- }
-
- ///
- /// Gets or sets custom padding for the input view, overriding the default padding.
- ///
- ///
- /// The following code demonstrates how to use the InputViewPadding property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public Thickness InputViewPadding
- {
- get { return (Thickness)GetValue(InputViewPaddingProperty); }
- set { SetValue(InputViewPaddingProperty, value); }
- }
-
- ///
- /// Gets or sets a value indicating whether this control is focused.
- ///
- bool IsLayoutFocused
- {
- get => (bool)GetValue(IsLayoutFocusedProperty);
- set => SetValue(IsLayoutFocusedProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether the hint label should always be floated, even when the input text is empty.
- /// The default value is false.
- ///
- /// true if the hint label should always be floated; otherwise, false.
- ///
- /// The following code demonstrates how to use the IsHintAlwaysFloated property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool IsHintAlwaysFloated
- {
- get => (bool)GetValue(IsHintAlwaysFloatedProperty);
- set => SetValue(IsHintAlwaysFloatedProperty, value);
- }
-
- ///
- /// Gets or sets the hint text displayed in the input view.
- ///
- ///
- /// The following code demonstrates how to use the Hint property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public string Hint
- {
- get => (string)GetValue(HintProperty);
- set => SetValue(HintProperty, value);
- }
-
- ///
- /// Gets or sets the helper text that provides additional information about the input.
- ///
- ///
- /// The following code demonstrates how to use the HelperText property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public string HelperText
- {
- get => (string)GetValue(HelperTextProperty);
- set => SetValue(HelperTextProperty, value);
- }
-
- ///
- /// Gets or sets the error text displayed when validation fails.
- ///
- /// Error messages are displayed below the input line, replacing the helper text until the error is fixed.
- ///
- /// The following code demonstrates how to use the ErrorText property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public string ErrorText
- {
- get => (string)GetValue(ErrorTextProperty);
- set => SetValue(ErrorTextProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether space is reserved for assistive labels such as
- /// helper text, error text, and character counters.
- ///
- ///
- /// true if space should be reserved for assistive labels; otherwise, false.
- ///
- ///
- /// If set to false, space will only be allocated based on the presence of helper text,
- /// error text, and character counter labels.
- ///
- ///
- /// The following code demonstrates how to use the ReserveSpaceForAssistiveLabels property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool ReserveSpaceForAssistiveLabels
- {
- get => (bool)GetValue(ReserveSpaceForAssistiveLabelsProperty);
- set => SetValue(ReserveSpaceForAssistiveLabelsProperty, value);
- }
-
- ///
- /// Gets or sets the type of container, which specifies the appearance of the background and its border.
- /// The default value is ContainerType.Filled.
- ///
- ///
- /// The following code demonstrates how to use the ContainerType property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public ContainerType ContainerType
- {
- get => (ContainerType)GetValue(ContainerTypeProperty);
- set => SetValue(ContainerTypeProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether to show the password visibility toggle.
- ///
- /// true if the password visibility toggle is enabled; otherwise, false.
- /// This property is supported only for control.
- ///
- /// The following code demonstrates how to use the EnablePasswordVisibilityToggle property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool EnablePasswordVisibilityToggle
- {
- get => (bool)GetValue(EnablePasswordVisibilityToggleProperty);
- set => SetValue(EnablePasswordVisibilityToggleProperty, value);
- }
-
- ///
- /// Gets a value for current active color based on input view's focus state.
- ///
- public Color CurrentActiveColor
- {
- get { return (Color)GetValue(CurrentActiveColorProperty); }
- internal set { SetValue(CurrentActiveColorKey, value); }
- }
-
- ///
- /// Gets or sets a value indicating whether the control is enabled and can interact with the user.
- /// The default value is true.
- ///
- /// true if the control is enabled; otherwise, false.
- ///
- /// The following code demonstrates how to use the IsEnabled property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public new bool IsEnabled
- {
- get { return (bool)GetValue(IsEnabledProperty); }
- set { SetValue(IsEnabledProperty, value); }
- }
-
- ///
- /// Gets or sets a value indicating whether the assistive label should float when the input is focused or unfocused.
- ///
- /// true if the label should float; otherwise, false.
- ///
- /// The following code demonstrates how to use the EnableFloating property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool EnableFloating
- {
- get => (bool)GetValue(EnableFloatingProperty);
- set => SetValue(EnableFloatingProperty, value);
- }
-
- ///
- /// Gets or sets a value indicating whether to enable animation for the hint text
- /// when the input view is focused or unfocused.
- ///
- /// true if hint animation is enabled; otherwise, false.
- ///
- /// The following code demonstrates how to use the EnableHintAnimation property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public bool EnableHintAnimation
- {
- get => (bool)GetValue(EnableHintAnimationProperty);
- set => SetValue(EnableHintAnimationProperty, value);
- }
-
- ///
- /// Gets or sets the color of the clear button.
- ///
- internal Color ClearButtonColor
- {
- get => (Color)GetValue(ClearButtonColorProperty);
- set => SetValue(ClearButtonColorProperty, value);
- }
-
- ///
- /// Gets or sets the style applied to the hint label.
- ///
- ///
- /// The following code demonstrates how to use the HintLabelStyle property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public LabelStyle HintLabelStyle
- {
- get { return (LabelStyle)GetValue(HintLabelStyleProperty); }
- set { SetValue(HintLabelStyleProperty, value); }
- }
-
- ///
- /// Gets or sets the style applied to the helper label.
- ///
- ///
- /// The following code demonstrates how to use the HelperLabelStyle property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public LabelStyle HelperLabelStyle
- {
- get { return (LabelStyle)GetValue(HelperLabelStyleProperty); }
- set { SetValue(HelperLabelStyleProperty, value); }
- }
-
- ///
- /// Gets or sets the style applied to the error label.
- ///
- /// This style is used to customize the appearance of the error message displayed below the input field.
- ///
- /// The following code demonstrates how to use the ErrorLabelStyle property in the .
- /// # [XAML](#tab/tabid-7)
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- /// # [C#](#tab/tabid-8)
- ///
- ///
- public LabelStyle ErrorLabelStyle
- {
- get { return (LabelStyle)GetValue(ErrorLabelStyleProperty); }
- set { SetValue(ErrorLabelStyleProperty, value); }
- }
-
- ///
- /// Gets or sets the style for counter label.
- ///
- internal LabelStyle CounterLabelStyle
- {
- get { return (LabelStyle)GetValue(CounterLabelStyleProperty); }
- set { SetValue(CounterLabelStyleProperty, value); }
- }
-
- ///
- /// Gets or sets the color value of Hint Text
- ///
- internal Color HintTextColor
- {
- get { return (Color)GetValue(HintTextColorProperty); }
- set { SetValue(HintTextColorProperty, value); }
- }
- ///
- /// Gets or sets the color value of Helper Text
- ///
- internal Color HelperTextColor
- {
- get { return (Color)GetValue(HelperTextColorProperty); }
- set { SetValue(HelperTextColorProperty, value); }
- }
-
- ///
- /// Gets or sets the color value of Outlined ContainerBackground
- ///
- internal Brush OutlinedContainerBackground
- {
- get { return (Brush)GetValue(OutlinedContainerBackgroundProperty); }
- set { SetValue(OutlinedContainerBackgroundProperty, value); }
- }
-
- ///
- /// Gets the value of the input text of the .
- ///
- public string Text { get; internal set; } = string.Empty;
-
- ///
- /// Gets a value indicating whether the background mode is outline.
- ///
- internal bool IsOutlined
- {
- get { return ContainerType == ContainerType.Outlined; }
- }
-
- ///
- /// Gets a value indicating whether the background mode is none.
- ///
- internal bool IsNone
- {
- get { return ContainerType == ContainerType.None; }
- }
-
- ///
- /// Gets a value indicating whether the background mode is filled.
- ///
- internal bool IsFilled
- {
- get { return ContainerType == ContainerType.Filled; }
- }
-
- ///
- /// Gets or sets a value indicating whether the hint was floated.
- ///
- /// This property is used to update the control UI based of hint state.
- internal bool IsHintFloated
- {
- get { return (bool)GetValue(IsHintFloatedProperty); }
- set { SetValue(IsHintFloatedProperty, value); }
- }
-
- ///
- /// Gets or sets the path to customize the appearance of the clear button.
- ///
- /// Specifies the ClearButtonPath. The default value is null
- internal Path ClearButtonPath
- {
- get { return (Path)GetValue(ClearButtonPathProperty); }
- set { SetValue(ClearButtonPathProperty, value); }
- }
-
- Color DisabledColor { get { return Color.FromUint(0x42000000); } }
-
- ///
- /// Gets a value indicating the size of hint.
- ///
- internal SizeF HintSize
- {
- get
- {
- if (string.IsNullOrEmpty(Hint) || HintLabelStyle == null)
- {
- return new Size(0);
- }
-
- _internalHintLabelStyle.FontSize = HintFontSize;
- var size = Hint.Measure(_internalHintLabelStyle);
- size.Height = GetHintLineCount(size.Width) * size.Height;
-
-
- return size;
- }
- }
-
- ///
- /// Gets a value indicating the size of floated hint.
- ///
- internal SizeF FloatedHintSize
- {
- get
- {
- if (string.IsNullOrEmpty(Hint) || HintLabelStyle == null || !ShowHint)
- {
- return new Size(0);
- }
-
- _internalHintLabelStyle.FontSize = FloatedHintFontSize;
- var size = Hint.Measure(_internalHintLabelStyle);
- size.Height = GetHintLineCount(size.Width) * size.Height;
- return size;
- }
- }
-
- ///
- /// Gets a value indicating the size of assistive text.
- ///
- internal SizeF CounterTextSize
- {
- get
- {
- if (string.IsNullOrEmpty(_counterText) || CounterLabelStyle == null)
- {
- return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
- }
- var size = Hint.Measure(_internalCounterLabelStyle);
- size.Width += DefaultAssistiveLabelPadding;
- return GetLabelSize(size);
- }
- }
-
- ///
- /// Gets a value indicating the size of helper text.
- ///
- internal SizeF HelperTextSize
- {
- get
- {
- if (string.IsNullOrEmpty(HelperText) || HelperLabelStyle == null)
- {
- return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
- }
- var size = HelperText.Measure(_internalHelperLabelStyle);
- size.Height = GetAssistiveTextLineCount(size.Width) * size.Height;
- return GetLabelSize(size);
- }
- }
-
- ///
- /// Gets a value indicating the size of Error text.
- ///
- internal SizeF ErrorTextSize
- {
- get
- {
- if (string.IsNullOrEmpty(ErrorText) || ErrorLabelStyle == null)
- {
- return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
- }
- var size = ErrorText.Measure(_internalErrorLabelStyle);
- size.Height = GetAssistiveTextLineCount(size.Width) * size.Height;
- return GetLabelSize(size);
- }
- }
-
- ///
- /// Gets the base line max height.
- ///
- internal double BaseLineMaxHeight => Math.Max(FocusedStrokeThickness, UnfocusedStrokeThickness);
-
- ///
- /// Gets a value indicating the top padding of the input view.
- ///
- double TopPadding
- {
- get
- {
- if (BaseLineMaxHeight <= 2)
- {
- return IsOutlined ? OutlinedPadding + (ShowHint ? (DefaultAssistiveTextHeight / 2) : (BaseLineMaxHeight * 2)) : IsFilled ? FilledTopPadding : NoneTopPadding;
- }
- return IsOutlined ? OutlinedPadding + (ShowHint ? (BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height / 2 : (BaseLineMaxHeight)) : IsFilled ? FilledTopPadding : NoneTopPadding;
- }
- }
-
- ///
- /// Gets a value indicating the bottom padding of the input view.
- ///
- double BottomPadding
- {
- get
- {
- if (BaseLineMaxHeight <= 2)
- {
- return (IsFilled ? FilledBottomPadding
- : IsOutlined ? OutlinedPadding : NoneBottomPadding) + (ReserveSpaceForAssistiveLabels ? TotalAssistiveTextHeight() + DefaultAssistiveLabelPadding : 0);
- }
- return (IsFilled ? FilledBottomPadding
- : IsOutlined ? OutlinedPadding : NoneBottomPadding) + BaseLineMaxHeight + (ReserveSpaceForAssistiveLabels ? TotalAssistiveTextHeight() + DefaultAssistiveLabelPadding : 0);
- }
- }
-
- ///
- /// Gets a value indicating the edge padding of the input view.
- ///
- double LeftPadding
- {
- get
- {
- if (BaseLineMaxHeight <= 2)
- {
- return IsNone ? 0 : EdgePadding + (IsOutlined ? BaseLineMaxHeight : 0);
- }
- return IsNone ? 0 : EdgePadding + (IsOutlined ? BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2 : 0);
- }
- }
-
- ///
- /// Gets the font size of the floated hint text.
- ///
- float FloatedHintFontSize
- {
- get
- {
- return (float)(HintFontSize * HintFontSizeScalingRatio);
- }
- }
-
- double AssistiveLabelPadding
- {
- get
- {
- return ReserveSpaceForAssistiveLabels ? DefaultAssistiveLabelPadding + BaseLineMaxHeight / 2 : BaseLineMaxHeight / 2;
- }
- }
-
- PathF ToggleIconPath
- {
- get
- {
- return _isPasswordTextVisible ? _pathBuilder.BuildPath(_toggleVisibleIconPath) : _pathBuilder.BuildPath(_toggleCollapsedIconPath);
- }
- }
-
- bool IsRTL
- {
- get { return ((this as IVisualElementController).EffectiveFlowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft; }
- }
-
- bool IsPassowordToggleIconVisible
- {
- get { return (EnablePasswordVisibilityToggle && Content is Entry); }
- }
- #endregion
-
- #region Property Changed Methods
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnLeadingViewChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.AddView(oldValue, newValue);
- inputLayout.UpdateLeadingViewPosition();
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnTrailingViewChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.AddView(oldValue, newValue);
- inputLayout.UpdateTrailingViewPosition();
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnHintAlwaysFloatedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && newValue is bool value)
- {
- if (!value && !string.IsNullOrEmpty(inputLayout.Text))
- {
- inputLayout.IsHintFloated = true;
- inputLayout.IsHintDownToUp = !inputLayout.IsHintFloated;
- inputLayout.InvalidateDrawable();
- return;
- }
-
- inputLayout.IsHintFloated = value;
- inputLayout.IsHintDownToUp = !inputLayout.IsHintFloated;
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnShowLeadingViewPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (newValue is bool value && bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.UpdateLeadingViewVisibility(value);
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnShowTrailingViewPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (newValue is bool value && bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.UpdateTrailingViewVisibility(value);
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnIsLayoutFocusedChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.ChangeVisualState();
- inputLayout.StartAnimation();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnHasErrorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.ChangeVisualState();
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnCharMaxLengthPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && newValue is int value)
- {
- inputLayout._counterText = $"0/{value}";
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Invoked whenever the is set.
- ///
- /// The bindable.
- /// The old value.
- /// The new value.
- static void OnEnablePasswordTogglePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && inputLayout.Content is Entry entry && newValue is bool value)
- {
- entry.IsPassword = value;
- inputLayout._isPasswordTextVisible = false;
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
-
- static void OnIsHintFloatedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && newValue is bool value)
- {
- if (inputLayout.Content is InputView || inputLayout.Content is Picker)
- {
- inputLayout.Content.Opacity = value ? 1 : 0;
- }
- }
- }
-
- static void OnInputViewPaddingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- if (inputLayout.Content != null)
- {
- inputLayout.UpdateContentMargin(inputLayout.Content);
- }
-
- if (inputLayout._initialLoaded)
- {
- inputLayout.UpdateViewBounds();
- }
- }
-
- }
-
- ///
- /// Raised when the property was changed.
- ///
- /// object
- /// object
- /// object
- static void OnEnabledPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.OnEnabledPropertyChanged((bool)newValue);
- }
- }
-
- static void OnHintLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- if (oldValue is LabelStyle oldLabelStyle)
- {
- oldLabelStyle.PropertyChanged -= inputLayout.OnHintLabelStylePropertyChanged;
- SetInheritedBindingContext(oldLabelStyle, null);
- oldLabelStyle.Parent = null;
- }
- if (newValue is LabelStyle newLabelStyle)
- {
- newLabelStyle.Parent = inputLayout;
- SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
- inputLayout._internalHintLabelStyle.TextColor = newLabelStyle.TextColor;
- inputLayout._internalHintLabelStyle.FontFamily = newLabelStyle.FontFamily;
- inputLayout._internalHintLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
- inputLayout.HintFontSize = (float)(newLabelStyle.FontSize < 12d ? inputLayout.FloatedHintFontSize : newLabelStyle.FontSize);
- newLabelStyle.PropertyChanged += inputLayout.OnHintLabelStylePropertyChanged;
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
- }
-
- static void OnHelperLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- if (oldValue is LabelStyle oldLabelStyle)
- {
- oldLabelStyle.PropertyChanged -= inputLayout.OnHelperLabelStylePropertyChanged;
- SetInheritedBindingContext(oldLabelStyle, inputLayout.BindingContext);
- oldLabelStyle.Parent = null;
- }
- if (newValue is LabelStyle newLabelStyle)
- {
- newLabelStyle.Parent = inputLayout;
- SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
- inputLayout._internalHelperLabelStyle.TextColor = newLabelStyle.TextColor;
- inputLayout._internalHelperLabelStyle.FontFamily = newLabelStyle.FontFamily;
- inputLayout._internalHelperLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
- inputLayout._internalHelperLabelStyle.FontSize = newLabelStyle.FontSize;
- newLabelStyle.PropertyChanged += inputLayout.OnHelperLabelStylePropertyChanged;
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
- }
-
- static void OnErrorLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- if (oldValue is LabelStyle oldLabelStyle)
- {
- oldLabelStyle.PropertyChanged -= inputLayout.OnErrorLabelStylePropertyChanged;
- SetInheritedBindingContext(oldLabelStyle, inputLayout.BindingContext);
- oldLabelStyle.Parent = null;
- }
- if (newValue is LabelStyle newLabelStyle)
- {
- newLabelStyle.Parent = inputLayout;
- SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
- inputLayout._internalErrorLabelStyle.TextColor = newLabelStyle.TextColor;
- inputLayout._internalErrorLabelStyle.FontFamily = newLabelStyle.FontFamily;
- inputLayout._internalErrorLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
- inputLayout._internalErrorLabelStyle.FontSize = newLabelStyle.FontSize;
- newLabelStyle.PropertyChanged += inputLayout.OnErrorLabelStylePropertyChanged;
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
- }
-
- static void OnCounterLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- if (oldValue is LabelStyle oldLabelStyle)
- {
- oldLabelStyle.PropertyChanged -= inputLayout.OnCounterLabelStylePropertyChanged;
- }
- if (newValue is LabelStyle newLabelStyle)
- {
- inputLayout._internalCounterLabelStyle.TextColor = newLabelStyle.TextColor;
- inputLayout._internalCounterLabelStyle.FontFamily = newLabelStyle.FontFamily;
- inputLayout._internalCounterLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
- inputLayout._internalCounterLabelStyle.FontSize = newLabelStyle.FontSize;
- newLabelStyle.PropertyChanged += inputLayout.OnCounterLabelStylePropertyChanged;
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- }
- }
- }
-
- static void OnReserveSpacePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && inputLayout.Content != null)
- {
- inputLayout.UpdateContentMargin(inputLayout.Content);
- }
- }
-
- static void OnContainerTypePropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && inputLayout.Content != null)
- {
- inputLayout.UpdateContentMargin(inputLayout.Content);
- if (inputLayout._initialLoaded)
- inputLayout.UpdateViewBounds();
- inputLayout.ChangeVisualState();
- }
- }
-
- static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout && inputLayout._initialLoaded)
- {
- inputLayout.UpdateViewBounds();
- }
- }
-
- ///
- /// This method triggers when the up and down button colors change.
- ///
- static void OnUpDownButtonColorChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.InvalidateDrawable();
- }
- }
-
- ///
- /// Property changed method for ClearButtonPath property
- ///
- ///
- ///
- ///
- static void OnClearButtonPathChanged(BindableObject bindable, object oldValue, object newValue)
- {
- if (bindable is SfTextInputLayout inputLayout)
- {
- inputLayout.InvalidateDrawable();
- }
- }
- #endregion
-
- }
+ public partial class SfTextInputLayout
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property indicates whether the layout has focus or not in the .
+ ///
+ static readonly BindableProperty IsLayoutFocusedProperty =
+ BindableProperty.Create(
+ nameof(IsLayoutFocused),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnIsLayoutFocusedChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the appearance of the background and border in the .
+ ///
+ public static readonly BindableProperty ContainerTypeProperty =
+ BindableProperty.Create(
+ nameof(ContainerType),
+ typeof(ContainerType),
+ typeof(SfTextInputLayout),
+ ContainerType.Filled,
+ BindingMode.Default,
+ null,
+ OnContainerTypePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property specifies the display of icons or other visual elements in the leading view of .
+ ///
+ public static readonly BindableProperty LeadingViewProperty =
+ BindableProperty.Create(
+ nameof(LeadingView),
+ typeof(View),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ OnLeadingViewChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property specifies the display of icons or other visual elements in the trailing view of .
+ ///
+ public static readonly BindableProperty TrailingViewProperty =
+ BindableProperty.Create(
+ nameof(TrailingView),
+ typeof(View),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ OnTrailingViewChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the leading view should be displayed in the .
+ ///
+ public static readonly BindableProperty ShowLeadingViewProperty =
+ BindableProperty.Create(
+ nameof(ShowLeadingView),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnShowLeadingViewPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the trailing view should be displayed in the .
+ ///
+ public static readonly BindableProperty ShowTrailingViewProperty =
+ BindableProperty.Create(
+ nameof(ShowTrailingView),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnShowTrailingViewPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the hint text should be displayed or not in the .
+ ///
+ public static readonly BindableProperty ShowHintProperty =
+ BindableProperty.Create(
+ nameof(ShowHint),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the character count should be displayed or not in the .
+ ///
+ internal static readonly BindableProperty ShowCharCountProperty =
+ BindableProperty.Create(
+ nameof(ShowCharCount),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false, BindingMode.Default,
+ null, OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the helper text should be displayed or not in the .
+ ///
+ public static readonly BindableProperty ShowHelperTextProperty =
+ BindableProperty.Create(
+ nameof(ShowHelperText),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.TwoWay,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the error text should be displayed or not in the .
+ ///
+ public static readonly BindableProperty HasErrorProperty =
+ BindableProperty.Create(
+ nameof(HasError),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnHasErrorPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the hint text should always float or not in the .
+ ///
+ public static readonly BindableProperty IsHintAlwaysFloatedProperty =
+ BindableProperty.Create(
+ nameof(IsHintAlwaysFloated),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnHintAlwaysFloatedPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines border color of the .
+ ///
+ public static readonly BindableProperty StrokeProperty =
+ BindableProperty.Create(
+ nameof(Stroke),
+ typeof(Color),
+ typeof(SfTextInputLayout),
+ _defaultStrokeColor,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines background color of the container in the .
+ ///
+ public static readonly BindableProperty ContainerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(ContainerBackground),
+ typeof(Brush),
+ typeof(SfTextInputLayout),
+ _defaultContainerBackground,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property specifies the corner radius of the outline border in the .
+ ///
+ public static readonly BindableProperty OutlineCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(OutlineCornerRadius),
+ typeof(double),
+ typeof(SfTextInputLayout),
+ 3.5d,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the stroke thickness of the border when the is focused.
+ ///
+ public static readonly BindableProperty FocusedStrokeThicknessProperty =
+ BindableProperty.Create(
+ nameof(FocusedStrokeThickness),
+ typeof(double),
+ typeof(SfTextInputLayout),
+ 2d,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines stroke thickness of the border when the is unfocused.
+ ///
+ public static readonly BindableProperty UnfocusedStrokeThicknessProperty =
+ BindableProperty.Create(
+ nameof(UnfocusedStrokeThickness),
+ typeof(double),
+ typeof(SfTextInputLayout),
+ 1d,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines maximum character length of the .
+ ///
+ public static readonly BindableProperty CharMaxLengthProperty =
+ BindableProperty.Create(
+ nameof(CharMaxLength),
+ typeof(int),
+ typeof(SfTextInputLayout),
+ int.MaxValue,
+ BindingMode.Default,
+ null,
+ OnCharMaxLengthPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines hint text in the .
+ ///
+ public static readonly BindableProperty HintProperty =
+ BindableProperty.Create(
+ nameof(Hint),
+ typeof(string),
+ typeof(SfTextInputLayout),
+ string.Empty,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines helper text of the .
+ ///
+ public static readonly BindableProperty HelperTextProperty =
+ BindableProperty.Create(
+ nameof(HelperText),
+ typeof(string),
+ typeof(SfTextInputLayout),
+ string.Empty,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines error text of the .
+ ///
+ public static readonly BindableProperty ErrorTextProperty =
+ BindableProperty.Create(
+ nameof(ErrorText),
+ typeof(string),
+ typeof(SfTextInputLayout),
+ string.Empty,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the reserved space for assistive labels such as helper text, error text, and character counters in the .
+ ///
+ public static readonly BindableProperty ReserveSpaceForAssistiveLabelsProperty =
+ BindableProperty.Create(
+ nameof(ReserveSpaceForAssistiveLabels),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnReserveSpacePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines leading view position in the .
+ ///
+ public static readonly BindableProperty LeadingViewPositionProperty =
+ BindableProperty.Create(
+ nameof(LeadingViewPosition),
+ typeof(ViewPosition),
+ typeof(SfTextInputLayout),
+ ViewPosition.Inside,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines trailing view position in the .
+ ///
+ public static readonly BindableProperty TrailingViewPositionProperty =
+ BindableProperty.Create(
+ nameof(TrailingViewPosition),
+ typeof(ViewPosition),
+ typeof(SfTextInputLayout),
+ ViewPosition.Inside,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines padding for the input view in the .
+ ///
+ public static readonly BindableProperty InputViewPaddingProperty =
+ BindableProperty.Create(
+ nameof(InputViewPadding),
+ typeof(Thickness),
+ typeof(SfTextInputLayout),
+ new Thickness(-1, -1, -1, -1),
+ BindingMode.Default,
+ null,
+ OnInputViewPaddingPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether need to display the password visibility toggle in the .
+ ///
+ public static readonly BindableProperty EnablePasswordVisibilityToggleProperty =
+ BindableProperty.Create(
+ nameof(EnablePasswordVisibilityToggle),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnEnablePasswordTogglePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the Label should float when the input is focused or unfocused in the .
+ ///
+ public static readonly BindableProperty EnableFloatingProperty =
+ BindableProperty.Create(
+ nameof(EnableFloating),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether need to enable the animation for hint text when input view is focused or unfocused in .
+ ///
+ public static readonly BindableProperty EnableHintAnimationProperty =
+ BindableProperty.Create(
+ nameof(EnableHintAnimation),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines color of the clear button in the .
+ ///
+ internal static readonly BindableProperty ClearButtonColorProperty =
+ BindableProperty.Create(
+ nameof(ClearButtonColor),
+ typeof(Color),
+ typeof(SfTextInputLayout),
+ ClearIconStrokeColor,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the Label style of the hint text in the .
+ ///
+ public static readonly BindableProperty HintLabelStyleProperty =
+ BindableProperty.Create(
+ nameof(HintLabelStyle),
+ typeof(LabelStyle),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ defaultValueCreator: bindale => GetHintLabelStyleDefaultValue(),
+ propertyChanged: OnHintLableStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the Label style of the helper text in the .
+ ///
+ public static readonly BindableProperty HelperLabelStyleProperty =
+ BindableProperty.Create(
+ nameof(HelperLabelStyle),
+ typeof(LabelStyle),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ defaultValueCreator: bindale => GetHelperLabelStyleDefaultValue(),
+ propertyChanged: OnHelperLableStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the Label style of the error text in the .
+ ///
+ public static readonly BindableProperty ErrorLabelStyleProperty =
+ BindableProperty.Create(
+ nameof(ErrorLabelStyle),
+ typeof(LabelStyle),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ defaultValueCreator: bindale => GetErrorLabelStyleDefaultValue(),
+ propertyChanged: OnErrorLableStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the Label style of the counter text in the .
+ ///
+ internal static readonly BindableProperty CounterLabelStyleProperty =
+ BindableProperty.Create(
+ nameof(CounterLabelStyle),
+ typeof(LabelStyle),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.Default,
+ null,
+ defaultValueCreator: bindale => GetCounterLabelStyleDefaultValue(),
+ propertyChanged: OnCounterLableStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the current active color of the .
+ ///
+ internal static readonly BindablePropertyKey CurrentActiveColorKey =
+ BindableProperty.CreateReadOnly(
+ nameof(CurrentActiveColor),
+ typeof(Color),
+ typeof(SfTextInputLayout),
+ Color.FromRgba("#79747E"),
+ BindingMode.Default,
+ null,
+ null);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the hint text is floated or not in the .
+ ///
+ internal static readonly BindableProperty IsHintFloatedProperty =
+ BindableProperty.Create(
+ nameof(IsHintFloated),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnIsHintFloatedPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines current active color of the .
+ ///
+ public static readonly BindableProperty CurrentActiveColorProperty = CurrentActiveColorKey.BindableProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the control is enabled or not in the .
+ ///
+ public static new readonly BindableProperty IsEnabledProperty =
+ BindableProperty.Create(
+ nameof(IsEnabled),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ true,
+ BindingMode.Default,
+ null,
+ OnEnabledPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the color of the hint text in .
+ ///
+ internal static readonly BindableProperty HintTextColorProperty =
+ BindableProperty.Create(
+ nameof(HintTextColor),
+ typeof(Color),
+ typeof(LabelStyle),
+ Color.FromArgb("#49454F"));
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the color of the helper text in .
+ ///
+ internal static readonly BindableProperty HelperTextColorProperty =
+ BindableProperty.Create(
+ nameof(HelperTextColor),
+ typeof(Color),
+ typeof(LabelStyle),
+ Color.FromArgb("#49454F"));
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines the background color of the outline container in .
+ ///
+ internal static readonly BindableProperty OutlinedContainerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(OutlinedContainerBackground),
+ typeof(Brush),
+ typeof(SfTextInputLayout),
+ new SolidColorBrush(Colors.Transparent));
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property allows for the customization of the appearance of clear button in the .
+ ///
+ internal static readonly BindableProperty ClearButtonPathProperty =
+ BindableProperty.Create(
+ nameof(ClearButtonPath),
+ typeof(Path),
+ typeof(SfTextInputLayout),
+ null,
+ BindingMode.OneWay,
+ null,
+ OnClearButtonPathChanged);
+
+ ///
+ /// Gets or sets a value that indicates whether to show the up down button.
+ ///
+ /// false if disable up down button; otherwise, true.
+ /// This property supports for SfNumericEntry control only.
+ internal static readonly BindableProperty ShowUpDownButtonProperty =
+ BindableProperty.Create(
+ nameof(ShowUpDownButton),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false, BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Gets or sets a value that indicates whether to show the clear button.
+ ///
+ /// false if disable clear button; otherwise, true.
+ /// This property supports for SfComboBox only.
+ internal static readonly BindableProperty ShowClearButtonProperty =
+ BindableProperty.Create(
+ nameof(ShowClearButton),
+ typeof(bool),
+ typeof(SfTextInputLayout),
+ false,
+ BindingMode.Default,
+ null,
+ OnPropertyChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ internal static readonly BindableProperty UpButtonColorProperty =
+ BindableProperty.Create(
+ nameof(UpButtonColor),
+ typeof(Color),
+ typeof(SfTextInputLayout),
+ Color.FromArgb("#49454F"),
+ BindingMode.Default, null,
+ OnUpDownButtonColorChanged);
+
+ ///
+ /// Identifies bindable property.
+ ///
+ /// The identifier for the bindable property.
+ internal static readonly BindableProperty DownButtonColorProperty =
+ BindableProperty.Create(
+ nameof(DownButtonColor),
+ typeof(Color),
+ typeof(SfTextInputLayout),
+ Color.FromArgb("#49454F"),
+ BindingMode.Default,
+ null,
+ OnUpDownButtonColorChanged);
+
+ ///
+ /// Returns the default label style for hint label.
+ ///
+ /// The LabelStyle
+ static LabelStyle GetHintLabelStyleDefaultValue()
+ {
+ return new LabelStyle() { FontSize = DefaultHintFontSize };
+ }
+
+ ///
+ /// Returns the default label style for helper label.
+ ///
+ /// The LabelStyle
+ static LabelStyle GetHelperLabelStyleDefaultValue()
+ {
+ return new LabelStyle();
+ }
+
+ ///
+ /// Returns the default label style for Error label.
+ ///
+ /// The LabelStyle
+ static LabelStyle GetErrorLabelStyleDefaultValue()
+ {
+ return new LabelStyle();
+ }
+
+ ///
+ /// Returns the default label style for counter label.
+ ///
+ /// The LabelStyle
+ static LabelStyle GetCounterLabelStyleDefaultValue()
+ {
+ return new LabelStyle();
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a value indicating whether the hint text should be displayed.
+ ///
+ /// true if the hint text should be displayed; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the ShowHint property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool ShowHint
+ {
+ get { return (bool)GetValue(ShowHintProperty); }
+ set { SetValue(ShowHintProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to display the character count when the value is changed.
+ ///
+ /// true if enable counter; otherwise, false.
+ internal bool ShowCharCount
+ {
+ get { return (bool)GetValue(ShowCharCountProperty); }
+ set { SetValue(ShowCharCountProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the helper text should be displayed.
+ ///
+ /// true if the helper text should be displayed; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the ShowHelperText property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool ShowHelperText
+ {
+ get => (bool)GetValue(ShowHelperTextProperty);
+ set => SetValue(ShowHelperTextProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the input has validation errors.
+ ///
+ /// true if there are validation errors; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the HasError property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool HasError
+ {
+ get => (bool)GetValue(HasErrorProperty);
+ set => SetValue(HasErrorProperty, value);
+ }
+
+ ///
+ /// Gets or sets the color of the border or base line, depending on the container type.
+ ///
+ ///
+ /// The following code demonstrates how to use the Stroke property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public Color Stroke
+ {
+ get => (Color)GetValue(StrokeProperty);
+ set => SetValue(StrokeProperty, value);
+ }
+
+ ///
+ /// Gets or sets the Hint text font size.
+ ///
+ internal float HintFontSize { get; set; }
+
+ ///
+ /// Gets or sets the background of the container.
+ ///
+ ///
+ /// The following code demonstrates how to use the ContainerBackground property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public Brush ContainerBackground
+ {
+ get => (Brush)GetValue(ContainerBackgroundProperty);
+ set => SetValue(ContainerBackgroundProperty, value);
+ }
+
+ ///
+ /// Gets or sets the corner radius of the outline border.
+ ///
+ /// It is applicable only for the outlined container type.
+ /// The default value is 4.
+ ///
+ /// The following code demonstrates how to use the OutlineCornerRadius property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public double OutlineCornerRadius
+ {
+ get => (double)GetValue(OutlineCornerRadiusProperty);
+ set => SetValue(OutlineCornerRadiusProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stroke thickness of the bottom line or outline border when control is in a focused state.
+ /// This property is applicable for both filled and outlined container types.
+ ///
+ /// The default value is 2.
+ ///
+ /// The following code demonstrates how to use the FocusedStrokeThickness property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public double FocusedStrokeThickness
+ {
+ get => (double)GetValue(FocusedStrokeThicknessProperty);
+ set => SetValue(FocusedStrokeThicknessProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stroke thickness for the bottom line or outline border when control is in an unfocused state
+ /// This property is applicable for filled and outlined container types.
+ ///
+ /// The default value is 1.
+ ///
+ /// The following code demonstrates how to use the UnfocusedStrokeThickness property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public double UnfocusedStrokeThickness
+ {
+ get => (double)GetValue(UnfocusedStrokeThicknessProperty);
+ set => SetValue(UnfocusedStrokeThicknessProperty, value);
+ }
+
+ ///
+ /// Gets or sets the maximum character length for the input. An error color is applied when this limit is exceeded.
+ ///
+ ///
+ /// The following code demonstrates how to use the UnfocusedStrokeThickness property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public int CharMaxLength
+ {
+ get => (int)GetValue(CharMaxLengthProperty);
+ set => SetValue(CharMaxLengthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the view to be displayed before the input view.
+ ///
+ /// This view is typically used to display icons or other visual elements.
+ ///
+ /// The following code demonstrates how to use the LeadingView property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public View LeadingView
+ {
+ get => (View)GetValue(LeadingViewProperty);
+ set => SetValue(LeadingViewProperty, value);
+ }
+
+ ///
+ /// Gets or sets the view to be displayed after the input view.
+ ///
+ /// This view is typically used to display icons or other visual elements.
+ ///
+ /// The following code demonstrates how to use the TrailingView property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public View TrailingView
+ {
+ get => (View)GetValue(TrailingViewProperty);
+ set => SetValue(TrailingViewProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the leading view should be displayed.
+ /// The default value is true.
+ ///
+ /// true if the leading view should be displayed; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the ShowLeadingView property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool ShowLeadingView
+ {
+ get => (bool)GetValue(ShowLeadingViewProperty);
+ set => SetValue(ShowLeadingViewProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the trailing view should be displayed.
+ /// The default value is true.
+ ///
+ /// true if the trailing view should be displayed; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the ShowTrailingView property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool ShowTrailingView
+ {
+ get => (bool)GetValue(ShowTrailingViewProperty);
+ set => SetValue(ShowTrailingViewProperty, value);
+ }
+
+ ///
+ /// Gets or sets the position of the leading view relative to the input layout.
+ /// The default value is ViewPosition.Inside.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public ViewPosition LeadingViewPosition
+ {
+ get => (ViewPosition)GetValue(LeadingViewPositionProperty);
+ set => SetValue(LeadingViewPositionProperty, value);
+ }
+
+ ///
+ /// Gets or sets the position of the trailing view relative to the input layout.
+ /// The default value is ViewPosition.Inside.
+ ///
+ ///
+ /// The following code demonstrates how to use the TrailingViewPosition property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public ViewPosition TrailingViewPosition
+ {
+ get => (ViewPosition)GetValue(TrailingViewPositionProperty);
+ set => SetValue(TrailingViewPositionProperty, value);
+ }
+
+ ///
+ /// Gets or sets custom padding for the input view, overriding the default padding.
+ ///
+ ///
+ /// The following code demonstrates how to use the InputViewPadding property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public Thickness InputViewPadding
+ {
+ get { return (Thickness)GetValue(InputViewPaddingProperty); }
+ set { SetValue(InputViewPaddingProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether this control is focused.
+ ///
+ bool IsLayoutFocused
+ {
+ get => (bool)GetValue(IsLayoutFocusedProperty);
+ set => SetValue(IsLayoutFocusedProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the hint label should always be floated, even when the input text is empty.
+ /// The default value is false.
+ ///
+ /// true if the hint label should always be floated; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the IsHintAlwaysFloated property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool IsHintAlwaysFloated
+ {
+ get => (bool)GetValue(IsHintAlwaysFloatedProperty);
+ set => SetValue(IsHintAlwaysFloatedProperty, value);
+ }
+
+ ///
+ /// Gets or sets the hint text displayed in the input view.
+ ///
+ ///
+ /// The following code demonstrates how to use the Hint property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public string Hint
+ {
+ get => (string)GetValue(HintProperty);
+ set => SetValue(HintProperty, value);
+ }
+
+ ///
+ /// Gets or sets the helper text that provides additional information about the input.
+ ///
+ ///
+ /// The following code demonstrates how to use the HelperText property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public string HelperText
+ {
+ get => (string)GetValue(HelperTextProperty);
+ set => SetValue(HelperTextProperty, value);
+ }
+
+ ///
+ /// Gets or sets the error text displayed when validation fails.
+ ///
+ /// Error messages are displayed below the input line, replacing the helper text until the error is fixed.
+ ///
+ /// The following code demonstrates how to use the ErrorText property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public string ErrorText
+ {
+ get => (string)GetValue(ErrorTextProperty);
+ set => SetValue(ErrorTextProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether space is reserved for assistive labels such as
+ /// helper text, error text, and character counters.
+ ///
+ ///
+ /// true if space should be reserved for assistive labels; otherwise, false.
+ ///
+ ///
+ /// If set to false, space will only be allocated based on the presence of helper text,
+ /// error text, and character counter labels.
+ ///
+ ///
+ /// The following code demonstrates how to use the ReserveSpaceForAssistiveLabels property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool ReserveSpaceForAssistiveLabels
+ {
+ get => (bool)GetValue(ReserveSpaceForAssistiveLabelsProperty);
+ set => SetValue(ReserveSpaceForAssistiveLabelsProperty, value);
+ }
+
+ ///
+ /// Gets or sets the type of container, which specifies the appearance of the background and its border.
+ /// The default value is ContainerType.Filled.
+ ///
+ ///
+ /// The following code demonstrates how to use the ContainerType property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public ContainerType ContainerType
+ {
+ get => (ContainerType)GetValue(ContainerTypeProperty);
+ set => SetValue(ContainerTypeProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show the password visibility toggle.
+ ///
+ /// true if the password visibility toggle is enabled; otherwise, false.
+ /// This property is supported only for control.
+ ///
+ /// The following code demonstrates how to use the EnablePasswordVisibilityToggle property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool EnablePasswordVisibilityToggle
+ {
+ get => (bool)GetValue(EnablePasswordVisibilityToggleProperty);
+ set => SetValue(EnablePasswordVisibilityToggleProperty, value);
+ }
+
+ ///
+ /// Gets a value for current active color based on input view's focus state.
+ ///
+ public Color CurrentActiveColor
+ {
+ get { return (Color)GetValue(CurrentActiveColorProperty); }
+ internal set { SetValue(CurrentActiveColorKey, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the control is enabled and can interact with the user.
+ /// The default value is true.
+ ///
+ /// true if the control is enabled; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the IsEnabled property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public new bool IsEnabled
+ {
+ get { return (bool)GetValue(IsEnabledProperty); }
+ set { SetValue(IsEnabledProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the assistive label should float when the input is focused or unfocused.
+ ///
+ /// true if the label should float; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the EnableFloating property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool EnableFloating
+ {
+ get => (bool)GetValue(EnableFloatingProperty);
+ set => SetValue(EnableFloatingProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to enable animation for the hint text
+ /// when the input view is focused or unfocused.
+ ///
+ /// true if hint animation is enabled; otherwise, false.
+ ///
+ /// The following code demonstrates how to use the EnableHintAnimation property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public bool EnableHintAnimation
+ {
+ get => (bool)GetValue(EnableHintAnimationProperty);
+ set => SetValue(EnableHintAnimationProperty, value);
+ }
+
+ ///
+ /// Gets or sets the color of the clear button.
+ ///
+ internal Color ClearButtonColor
+ {
+ get => (Color)GetValue(ClearButtonColorProperty);
+ set => SetValue(ClearButtonColorProperty, value);
+ }
+
+ ///
+ /// Gets or sets the style applied to the hint label.
+ ///
+ ///
+ /// The following code demonstrates how to use the HintLabelStyle property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public LabelStyle HintLabelStyle
+ {
+ get { return (LabelStyle)GetValue(HintLabelStyleProperty); }
+ set { SetValue(HintLabelStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the style applied to the helper label.
+ ///
+ ///
+ /// The following code demonstrates how to use the HelperLabelStyle property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public LabelStyle HelperLabelStyle
+ {
+ get { return (LabelStyle)GetValue(HelperLabelStyleProperty); }
+ set { SetValue(HelperLabelStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the style applied to the error label.
+ ///
+ /// This style is used to customize the appearance of the error message displayed below the input field.
+ ///
+ /// The following code demonstrates how to use the ErrorLabelStyle property in the .
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-8)
+ ///
+ ///
+ public LabelStyle ErrorLabelStyle
+ {
+ get { return (LabelStyle)GetValue(ErrorLabelStyleProperty); }
+ set { SetValue(ErrorLabelStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the style for counter label.
+ ///
+ internal LabelStyle CounterLabelStyle
+ {
+ get { return (LabelStyle)GetValue(CounterLabelStyleProperty); }
+ set { SetValue(CounterLabelStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color value of Hint Text
+ ///
+ internal Color HintTextColor
+ {
+ get { return (Color)GetValue(HintTextColorProperty); }
+ set { SetValue(HintTextColorProperty, value); }
+ }
+ ///
+ /// Gets or sets the color value of Helper Text
+ ///
+ internal Color HelperTextColor
+ {
+ get { return (Color)GetValue(HelperTextColorProperty); }
+ set { SetValue(HelperTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color value of Outlined ContainerBackground
+ ///
+ internal Brush OutlinedContainerBackground
+ {
+ get { return (Brush)GetValue(OutlinedContainerBackgroundProperty); }
+ set { SetValue(OutlinedContainerBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show the UpDown buttons.
+ ///
+ /// true if UpDown button is visible; otherwise, false.
+ /// This property supports for NumericEntry control only.
+ internal bool ShowUpDownButton
+ {
+ get => (bool)this.GetValue(ShowUpDownButtonProperty);
+ set => this.SetValue(ShowUpDownButtonProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show the clear button.
+ ///
+ /// false if disable clear button; otherwise, true.
+ /// This property supports for SfCombobox and SfAutoComplete only.
+ internal bool ShowClearButton
+ {
+ get => (bool)this.GetValue(ShowClearButtonProperty);
+ set => this.SetValue(ShowClearButtonProperty, value);
+ }
+
+ ///
+ /// Gets or sets the color of the up button.
+ ///
+ /// Specifies the value for up button. The default value is Colors.Black.
+ internal Color UpButtonColor
+ {
+ get => (Color)this.GetValue(UpButtonColorProperty);
+ set => this.SetValue(UpButtonColorProperty, value);
+ }
+
+ ///
+ /// Gets or sets the color of the down button.
+ ///
+ /// Specifies the value for down button. The default value is Colors.Black.
+ internal Color DownButtonColor
+ {
+ get => (Color)this.GetValue(DownButtonColorProperty);
+ set => this.SetValue(DownButtonColorProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the alignment of the up-down button is vertical or not.
+ ///
+ internal bool IsUpDownVerticalAlignment
+ {
+ get { return _isUpDownVerticalAlignment; }
+ set
+ {
+ if (value != _isUpDownVerticalAlignment)
+ {
+ _isUpDownVerticalAlignment = value;
+ this.InvalidateMeasureOverride();
+ }
+ }
+ }
+
+ ///
+ /// Gets the value of the input text of the .
+ ///
+ public string Text { get; internal set; } = string.Empty;
+
+ ///
+ /// Gets a value indicating whether the background mode is outline.
+ ///
+ internal bool IsOutlined
+ {
+ get { return ContainerType == ContainerType.Outlined; }
+ }
+
+ ///
+ /// Gets a value indicating whether the background mode is none.
+ ///
+ internal bool IsNone
+ {
+ get { return ContainerType == ContainerType.None; }
+ }
+
+ ///
+ /// Gets a value indicating whether the background mode is filled.
+ ///
+ internal bool IsFilled
+ {
+ get { return ContainerType == ContainerType.Filled; }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the hint was floated.
+ ///
+ /// This property is used to update the control UI based of hint state.
+ internal bool IsHintFloated
+ {
+ get { return (bool)GetValue(IsHintFloatedProperty); }
+ set { SetValue(IsHintFloatedProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the path to customize the appearance of the clear button.
+ ///
+ /// Specifies the ClearButtonPath. The default value is null
+ internal Path ClearButtonPath
+ {
+ get { return (Path)GetValue(ClearButtonPathProperty); }
+ set { SetValue(ClearButtonPathProperty, value); }
+ }
+
+ Color DisabledColor { get { return Color.FromUint(0x42000000); } }
+
+ ///
+ /// Gets a value indicating the size of hint.
+ ///
+ internal SizeF HintSize
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(Hint) || HintLabelStyle == null)
+ {
+ return new Size(0);
+ }
+
+ _internalHintLabelStyle.FontSize = HintFontSize;
+ var size = Hint.Measure(_internalHintLabelStyle);
+ size.Height = GetHintLineCount(size.Width) * size.Height;
+
+
+ return size;
+ }
+ }
+
+ ///
+ /// Gets a value indicating the size of floated hint.
+ ///
+ internal SizeF FloatedHintSize
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(Hint) || HintLabelStyle == null || !ShowHint)
+ {
+ return new Size(0);
+ }
+
+ _internalHintLabelStyle.FontSize = FloatedHintFontSize;
+ var size = Hint.Measure(_internalHintLabelStyle);
+ size.Height = GetHintLineCount(size.Width) * size.Height;
+ return size;
+ }
+ }
+
+ ///
+ /// Gets a value indicating the size of assistive text.
+ ///
+ internal SizeF CounterTextSize
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(_counterText) || CounterLabelStyle == null)
+ {
+ return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
+ }
+ var size = Hint.Measure(_internalCounterLabelStyle);
+ size.Width += DefaultAssistiveLabelPadding;
+ return GetLabelSize(size);
+ }
+ }
+
+ ///
+ /// Gets a value indicating the size of helper text.
+ ///
+ internal SizeF HelperTextSize
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(HelperText) || HelperLabelStyle == null)
+ {
+ return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
+ }
+ var size = HelperText.Measure(_internalHelperLabelStyle);
+ size.Height = GetAssistiveTextLineCount(size.Width) * size.Height;
+ return GetLabelSize(size);
+ }
+ }
+
+ ///
+ /// Gets a value indicating the size of Error text.
+ ///
+ internal SizeF ErrorTextSize
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(ErrorText) || ErrorLabelStyle == null)
+ {
+ return GetLabelSize(new Size(0, DefaultAssistiveTextHeight));
+ }
+ var size = ErrorText.Measure(_internalErrorLabelStyle);
+ size.Height = GetAssistiveTextLineCount(size.Width) * size.Height;
+ return GetLabelSize(size);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the alignment of the up-down button alignment is left.
+ ///
+ internal bool IsUpDownAlignmentLeft
+ {
+ get { return _isUpDownAlignmentLeft; }
+ set
+ {
+ if (value != _isUpDownAlignmentLeft)
+ {
+ this._isUpDownAlignmentLeft = value;
+ this.InvalidateMeasureOverride();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the alignment of the up-down button alignment is both.
+ ///
+ internal bool IsUpDownAlignmentBoth
+ {
+ get { return _isUpDownAlignmentBoth; }
+ set
+ {
+ if (value != _isUpDownAlignmentBoth)
+ {
+ this._isUpDownAlignmentBoth = value;
+ this.InvalidateMeasureOverride();
+ }
+ }
+ }
+
+ ///
+ /// Gets the base line max height.
+ ///
+ internal double BaseLineMaxHeight => Math.Max(FocusedStrokeThickness, UnfocusedStrokeThickness);
+
+ ///
+ /// Gets a value indicating the top padding of the input view.
+ ///
+ double TopPadding
+ {
+ get
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ return IsOutlined ? OutlinedPadding + (ShowHint ? (DefaultAssistiveTextHeight / 2) : (BaseLineMaxHeight * 2)) : IsFilled ? FilledTopPadding : NoneTopPadding;
+ }
+ return IsOutlined ? OutlinedPadding + (ShowHint ? (BaseLineMaxHeight > FloatedHintSize.Height / 2) ? BaseLineMaxHeight : FloatedHintSize.Height / 2 : (BaseLineMaxHeight)) : IsFilled ? FilledTopPadding : NoneTopPadding;
+ }
+ }
+
+ ///
+ /// Gets a value indicating the bottom padding of the input view.
+ ///
+ double BottomPadding
+ {
+ get
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ return (IsFilled ? FilledBottomPadding
+ : IsOutlined ? OutlinedPadding : NoneBottomPadding) + (ReserveSpaceForAssistiveLabels ? TotalAssistiveTextHeight() + DefaultAssistiveLabelPadding : 0);
+ }
+ return (IsFilled ? FilledBottomPadding
+ : IsOutlined ? OutlinedPadding : NoneBottomPadding) + BaseLineMaxHeight + (ReserveSpaceForAssistiveLabels ? TotalAssistiveTextHeight() + DefaultAssistiveLabelPadding : 0);
+ }
+ }
+
+ ///
+ /// Gets a value indicating the edge padding of the input view.
+ ///
+ double LeftPadding
+ {
+ get
+ {
+ if (BaseLineMaxHeight <= 2)
+ {
+ return IsNone ? 0 : EdgePadding + (IsOutlined ? BaseLineMaxHeight : 0);
+ }
+ return IsNone ? 0 : EdgePadding + (IsOutlined ? BaseLineMaxHeight - DefaultAssistiveLabelPadding / 2 : 0);
+ }
+ }
+
+ ///
+ /// Gets the font size of the floated hint text.
+ ///
+ float FloatedHintFontSize
+ {
+ get
+ {
+ return (float)(HintFontSize * HintFontSizeScalingRatio);
+ }
+ }
+
+ double AssistiveLabelPadding
+ {
+ get
+ {
+ return ReserveSpaceForAssistiveLabels ? DefaultAssistiveLabelPadding + BaseLineMaxHeight / 2 : BaseLineMaxHeight / 2;
+ }
+ }
+
+ PathF ToggleIconPath
+ {
+ get
+ {
+ return _isPasswordTextVisible ? _pathBuilder.BuildPath(_toggleVisibleIconPath) : _pathBuilder.BuildPath(_toggleCollapsedIconPath);
+ }
+ }
+
+ bool IsRTL
+ {
+ get { return ((this as IVisualElementController).EffectiveFlowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft; }
+ }
+
+ bool IsPassowordToggleIconVisible
+ {
+ get { return (EnablePasswordVisibilityToggle && Content is Entry); }
+ }
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnLeadingViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.AddView(oldValue, newValue);
+ inputLayout.UpdateLeadingViewPosition();
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnTrailingViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.AddView(oldValue, newValue);
+ inputLayout.UpdateTrailingViewPosition();
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnHintAlwaysFloatedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && newValue is bool value)
+ {
+ if (!value && !string.IsNullOrEmpty(inputLayout.Text))
+ {
+ inputLayout.IsHintFloated = true;
+ inputLayout.IsHintDownToUp = !inputLayout.IsHintFloated;
+ inputLayout.InvalidateDrawable();
+ return;
+ }
+
+ inputLayout.IsHintFloated = value;
+ inputLayout.IsHintDownToUp = !inputLayout.IsHintFloated;
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnShowLeadingViewPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (newValue is bool value && bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.UpdateLeadingViewVisibility(value);
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnShowTrailingViewPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (newValue is bool value && bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.UpdateTrailingViewVisibility(value);
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnIsLayoutFocusedChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.ChangeVisualState();
+ inputLayout.StartAnimation();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnHasErrorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.ChangeVisualState();
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnCharMaxLengthPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && newValue is int value)
+ {
+ inputLayout._counterText = $"0/{value}";
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever the is set.
+ ///
+ /// The bindable.
+ /// The old value.
+ /// The new value.
+ static void OnEnablePasswordTogglePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && inputLayout.Content is Entry entry && newValue is bool value)
+ {
+ entry.IsPassword = value;
+ inputLayout._isPasswordTextVisible = false;
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+
+ static void OnIsHintFloatedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && newValue is bool value)
+ {
+ if (inputLayout.Content is InputView || inputLayout.Content is Picker)
+ {
+ inputLayout.Content.Opacity = value ? 1 : 0;
+ }
+ else if (inputLayout != null && inputLayout.Content is SfView numericEntry && numericEntry.Children.Count > 0)
+ {
+ if (numericEntry.Children[0] is Entry numericInputView)
+ {
+ numericInputView.Opacity = value ? 1 : 0;
+ }
+ }
+ }
+
+ }
+
+ static void OnInputViewPaddingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ if (inputLayout.Content != null)
+ {
+ inputLayout.UpdateContentMargin(inputLayout.Content);
+ }
+
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+
+ }
+
+ ///
+ /// Raised when the property was changed.
+ ///
+ /// object
+ /// object
+ /// object
+ static void OnEnabledPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.OnEnabledPropertyChanged((bool)newValue);
+ }
+ }
+
+ static void OnHintLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ if (oldValue is LabelStyle oldLabelStyle)
+ {
+ oldLabelStyle.PropertyChanged -= inputLayout.OnHintLabelStylePropertyChanged;
+ SetInheritedBindingContext(oldLabelStyle, null);
+ oldLabelStyle.Parent = null;
+ }
+ if (newValue is LabelStyle newLabelStyle)
+ {
+ newLabelStyle.Parent = inputLayout;
+ SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
+ inputLayout._internalHintLabelStyle.TextColor = newLabelStyle.TextColor;
+ inputLayout._internalHintLabelStyle.FontFamily = newLabelStyle.FontFamily;
+ inputLayout._internalHintLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
+ inputLayout.HintFontSize = (float)(newLabelStyle.FontSize < 12d ? inputLayout.FloatedHintFontSize : newLabelStyle.FontSize);
+ newLabelStyle.PropertyChanged += inputLayout.OnHintLabelStylePropertyChanged;
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+ }
+
+ static void OnHelperLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ if (oldValue is LabelStyle oldLabelStyle)
+ {
+ oldLabelStyle.PropertyChanged -= inputLayout.OnHelperLabelStylePropertyChanged;
+ SetInheritedBindingContext(oldLabelStyle, inputLayout.BindingContext);
+ oldLabelStyle.Parent = null;
+ }
+ if (newValue is LabelStyle newLabelStyle)
+ {
+ newLabelStyle.Parent = inputLayout;
+ SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
+ inputLayout._internalHelperLabelStyle.TextColor = newLabelStyle.TextColor;
+ inputLayout._internalHelperLabelStyle.FontFamily = newLabelStyle.FontFamily;
+ inputLayout._internalHelperLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
+ inputLayout._internalHelperLabelStyle.FontSize = newLabelStyle.FontSize;
+ newLabelStyle.PropertyChanged += inputLayout.OnHelperLabelStylePropertyChanged;
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+ }
+
+ static void OnErrorLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ if (oldValue is LabelStyle oldLabelStyle)
+ {
+ oldLabelStyle.PropertyChanged -= inputLayout.OnErrorLabelStylePropertyChanged;
+ SetInheritedBindingContext(oldLabelStyle, inputLayout.BindingContext);
+ oldLabelStyle.Parent = null;
+ }
+ if (newValue is LabelStyle newLabelStyle)
+ {
+ newLabelStyle.Parent = inputLayout;
+ SetInheritedBindingContext(newLabelStyle, inputLayout.BindingContext);
+ inputLayout._internalErrorLabelStyle.TextColor = newLabelStyle.TextColor;
+ inputLayout._internalErrorLabelStyle.FontFamily = newLabelStyle.FontFamily;
+ inputLayout._internalErrorLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
+ inputLayout._internalErrorLabelStyle.FontSize = newLabelStyle.FontSize;
+ newLabelStyle.PropertyChanged += inputLayout.OnErrorLabelStylePropertyChanged;
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+ }
+
+ static void OnCounterLableStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ if (oldValue is LabelStyle oldLabelStyle)
+ {
+ oldLabelStyle.PropertyChanged -= inputLayout.OnCounterLabelStylePropertyChanged;
+ }
+ if (newValue is LabelStyle newLabelStyle)
+ {
+ inputLayout._internalCounterLabelStyle.TextColor = newLabelStyle.TextColor;
+ inputLayout._internalCounterLabelStyle.FontFamily = newLabelStyle.FontFamily;
+ inputLayout._internalCounterLabelStyle.FontAttributes = newLabelStyle.FontAttributes;
+ inputLayout._internalCounterLabelStyle.FontSize = newLabelStyle.FontSize;
+ newLabelStyle.PropertyChanged += inputLayout.OnCounterLabelStylePropertyChanged;
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+ }
+ }
+
+ static void OnReserveSpacePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && inputLayout.Content != null)
+ {
+ inputLayout.UpdateContentMargin(inputLayout.Content);
+ }
+ }
+
+ static void OnContainerTypePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && inputLayout.Content != null)
+ {
+ inputLayout.UpdateContentMargin(inputLayout.Content);
+ if (inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+
+ inputLayout.ChangeVisualState();
+ }
+ }
+
+ static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout && inputLayout._initialLoaded)
+ {
+ inputLayout.UpdateViewBounds();
+ }
+ }
+
+ ///
+ /// This method triggers when the up and down button colors change.
+ ///
+ static void OnUpDownButtonColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Property changed method for ClearButtonPath property
+ ///
+ ///
+ ///
+ ///
+ static void OnClearButtonPathChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout.InvalidateDrawable();
+ }
+ }
+ #endregion
+
+ }
}
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.cs b/maui/src/TextInputLayout/SfTextInputLayout.cs
index c3ae0f3d..87a19011 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.cs
@@ -9,6 +9,9 @@
using Syncfusion.Maui.Toolkit.Internals;
using Syncfusion.Maui.Toolkit.Themes;
using PointerEventArgs = Syncfusion.Maui.Toolkit.Internals.PointerEventArgs;
+using Syncfusion.Maui.Toolkit.NumericEntry;
+using Syncfusion.Maui.Toolkit.EntryRenderer;
+using Syncfusion.Maui.Toolkit.EntryView;
namespace Syncfusion.Maui.Toolkit.TextInputLayout
{
@@ -18,7 +21,7 @@ namespace Syncfusion.Maui.Toolkit.TextInputLayout
///
///
///
+ ///
///
///
/// ]]>
@@ -27,7 +30,17 @@ namespace Syncfusion.Maui.Toolkit.TextInputLayout
[ContentProperty("Content")]
public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentThemeElement
{
- #region Fields
+ #region Fields
+
+ ///
+ /// Timer for handling long press events.
+ ///
+ bool _isPressOccurring = false;
+
+ ///
+ /// Gets or sets a value updown button alignment is vertical or not.
+ ///
+ bool _isUpDownVerticalAlignment = false;
///
/// Gets the padding value of the helper text, error text, counter text.
@@ -201,20 +214,30 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
///
internal bool IsHintDownToUp = true;
+ ///
+ /// Gets or sets a value indicating whether the alignment of the up-down button alignment is left.
+ ///
+ bool _isUpDownAlignmentLeft = false;
+
///
- /// Gets or sets the start value for custom animation.
+ /// Gets or sets a value indicating whether the alignment of the up-down button alignment is Both.
///
- double _translateStart = 0;
+ private bool _isUpDownAlignmentBoth = false;
///
- /// Gets or sets the end value for custom animation.
+ /// Gets or sets the vertical start value for custom animation.
///
- double _translateEnd = 1;
+ double _translateStart = 0;
///
- /// Gets or sets the start value for scaling animation.
+ /// Gets or sets the vertical end value for custom animation.
///
- double _fontSizeStart = DefaultHintFontSize;
+ double _translateEnd = 1;
+
+ ///
+ /// Gets or sets the start value for scaling animation.
+ ///
+ double _fontSizeStart = DefaultHintFontSize;
///
/// Gets or sets the end value for scaling animation.
@@ -259,6 +282,12 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
RectF _counterTextRect = new();
+ RectF _downIconRectF = new();
+
+ RectF _upIconRectF = new();
+
+ IUpDownButtonRenderer? _tilRenderer;
+
readonly LabelStyle _internalHintLabelStyle = new();
readonly LabelStyle _internalHelperLabelStyle = new();
@@ -285,6 +314,11 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
Rect? _oldBounds;
#elif IOS || MACCATALYST
Point _touchDownPoint;
+
+ ///
+ /// To access to the native iOS text control.
+ ///
+ UIKit.UITextField? uiEntry;
#endif
#endregion
@@ -303,6 +337,8 @@ public SfTextInputLayout()
_effectsRenderer = new EffectsRenderer(this);
Unloaded += OnTextInputLayoutUnloaded;
Loaded += OnTextInputLayoutLoaded;
+ SetRendererBasedOnPlatform();
+
}
#endregion
@@ -323,24 +359,73 @@ Point GetTouchPoint(PointerEventArgs e)
return touchPoint;
}
- async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
+#if IOS || MACCATALYST
+ async void UpDownButtonPressed(Point touchpoint)
+#else
+ void UpDownButtonPressed(Point touchpoint)
+#endif
+ {
+ if (this.Content is ITextInputLayout numericEntry)
+ {
+ if ((_downIconRectF.Contains(touchpoint) && IsUpDownVerticalAlignment) || (_upIconRectF.Contains(touchpoint) && !IsUpDownVerticalAlignment))
+ {
+ numericEntry.UpButtonPressed();
+ }
+ else if ((_upIconRectF.Contains(touchpoint) && IsUpDownVerticalAlignment) || (_downIconRectF.Contains(touchpoint) && !IsUpDownVerticalAlignment))
+ {
+ numericEntry.DownButtonPressed();
+ }
+ }
+#if IOS || MACCATALYST
+ await Task.Delay(10);
+ IsIconPressed = false;
+#endif
+ }
+
+#if IOS || MACCATALYST
+ async void ClearText()
+#else
+ void ClearText()
+#endif
+ {
+ if (this.Content is ITextInputLayout numericEntry)
+ {
+ numericEntry.ClearIconPressed();
+ if (!IsClearIconVisible && _effectsRenderer != null && _effectsRenderer.HighlightBounds.Width > 0 && _effectsRenderer.HighlightBounds.Height > 0)
+ {
+ _effectsRenderer.RemoveHighlight();
+ }
+ }
+#if IOS || MACCATALYST
+ await Task.Delay(10);
+ IsIconPressed = false;
+#endif
+ }
+ async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
{
if (e.Action == PointerActions.Cancelled || e.Action == PointerActions.Exited)
{
+ _isPressOccurring = false;
IsLayoutTapped = false;
}
#if WINDOWS
IsIconPressed = false;
#endif
+ if (e.Action == PointerActions.Pressed)
+ {
#if IOS || MACCATALYST
- if (e.Action == PointerActions.Pressed)
- {
- _touchDownPoint = e.TouchPoint;
- }
+ _touchDownPoint = e.TouchPoint;
#endif
- if (e.Action == PointerActions.Released)
+ if (IsUpDownButtonPressed(touchPoint))
+ {
+ StartPressTimer(touchPoint);
+ UpDownButtonPressed(touchPoint);
+ }
+ }
+ if (e.Action == PointerActions.Released)
{
+ _isPressOccurring = false;
#if IOS || MACCATALYST
double diffX = Math.Abs(_touchDownPoint.X - e.TouchPoint.X);
double diffY = Math.Abs(_touchDownPoint.Y - e.TouchPoint.Y);
@@ -350,6 +435,22 @@ async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
return;
}
#endif
+ if (IsClearIconVisible && _clearIconRectF.Contains(touchPoint) && (IsLayoutFocused))
+ {
+#if !ANDROID
+ IsIconPressed = true;
+#endif
+ ClearText();
+ return;
+ }
+ if (IsUpDownButtonPressed(touchPoint))
+ {
+#if WINDOWS
+ IsIconPressed = true;
+#endif
+ return;
+ }
+
if ((EnablePasswordVisibilityToggle) && _passwordToggleIconRectF.Contains(touchPoint))
{
#if !ANDROID
@@ -359,7 +460,6 @@ async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
{
ToggleIcon();
}
-
return;
}
@@ -384,6 +484,10 @@ async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
{
inputView.Focus();
}
+ else if (Content is SfEntryView entryView && !entryView.IsReadOnly && entryView.IsEnabled)
+ {
+ entryView.Focus();
+ }
else if (Content is SfView sfView && sfView.Children.Count > 0 && sfView.Children[0] is Entry entry)
{
entry.Focus();
@@ -393,11 +497,44 @@ async Task HandlePointerActions(PointerEventArgs e, Point touchPoint)
Content.Focus();
}
}
+ }
+ }
+ }
+
+
+ ///
+ /// Determines whether the specified touch point intersects with the up or down button.
+ ///
+ /// The point of touch input to evaluate.
+ ///
+ /// True if the touch point is within the bounds of the up or down button; otherwise, false.
+ ///
+ bool IsUpDownButtonPressed(Point touchPoint)
+ {
+ return ShowUpDownButton && ((_downIconRectF.Contains(touchPoint) || _upIconRectF.Contains(touchPoint)));
+ }
+
+ void HandleEntryView(object newValue)
+ {
+ if (newValue is SfView numericEntry && numericEntry.Children.Count > 0)
+ {
+ numericEntry.BackgroundColor = Colors.Transparent;
+ if (numericEntry.Children[0] is Entry numericInputView)
+ {
+ numericInputView.Focused += OnTextInputViewFocused;
+ numericInputView.Unfocused += OnTextInputViewUnfocused;
+ numericInputView.TextChanged += OnTextInputViewTextChanged;
+ numericInputView.HandlerChanged += OnTextInputViewHandlerChanged;
+ if (!string.IsNullOrEmpty(numericInputView.Text))
+ {
+ IsHintFloated = true;
+ IsHintDownToUp = false;
+ Text = numericInputView.Text;
+ }
}
}
}
-
void HandleInputView(object newValue)
{
if (newValue is InputView inputView)
@@ -488,7 +625,7 @@ void OnPickerSelectedIndexChanged(object? sender, EventArgs e)
}
InvalidateDrawable();
}
- #endregion
+#endregion
#region Override Methods
@@ -501,7 +638,7 @@ void OnPickerSelectedIndexChanged(object? sender, EventArgs e)
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
- if (!VisualStateManager.HasVisualStateGroups(this) && !HasStrokeValue() && !HasContainerBackgroundValue() && !Application.Current!.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme))
+ if (!VisualStateManager.HasVisualStateGroups(this) && HasStrokeValue() && HasContainerBackgroundValue() && !Application.Current!.Resources.TryGetValue("SfTextInputLayoutTheme", out var theme))
{
AddDefaultVSM();
}
@@ -515,10 +652,12 @@ protected override void OnSizeAllocated(double width, double height)
///
protected override void OnContentChanged(object oldValue, object newValue)
{
+ ResetNumericUpDown();
HandleInputView(newValue);
HandlePickerView(newValue);
HandleDatePickerView(newValue);
HandleTimePickerView(newValue);
+ HandleEntryView(newValue);
if (newValue is View view)
{
@@ -530,6 +669,13 @@ protected override void OnContentChanged(object oldValue, object newValue)
{
entryEditorContent.Opacity = IsHintFloated ? 1 : 0;
}
+ else if (newValue is SfView numericEntryContent && numericEntryContent.Children.Count > 0)
+ {
+ if (numericEntryContent.Children[0] is Entry numericInputView)
+ {
+ numericInputView.Opacity = IsHintFloated ? 1 : 0;
+ }
+ }
else if (newValue is Picker picker)
{
if (DeviceInfo.Platform != DevicePlatform.WinUI)
@@ -566,9 +712,9 @@ protected override Size MeasureContent(double widthConstraint, double heightCons
if (Content != null)
{
#if NET8_0
- measure = this.Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
+ measure = Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
#else
- measure = this.Content.Measure(widthConstraint, heightConstraint);
+ measure = Content.Measure(widthConstraint, heightConstraint);
#endif
}
@@ -583,6 +729,10 @@ protected override Size MeasureContent(double widthConstraint, double heightCons
if (heightConstraint == -1 || heightConstraint == double.PositiveInfinity)
{
measuredHeight = measure.Height;
+ if (IsUpDownVerticalAlignment && measuredHeight < 90 && ShowUpDownButton)
+ {
+ measuredHeight = 90;
+ }
}
else
{
@@ -631,6 +781,205 @@ protected override Size ArrangeContent(Rect bounds)
}
#endif
return base.ArrangeContent(bounds);
+ }
+
+ ///
+ /// Resets the numeric up-down control settings
+ ///
+ void ResetNumericUpDown()
+ {
+ ShowUpDownButton = false;
+ ShowClearButton = false;
+ IsUpDownVerticalAlignment = false;
+ IsUpDownAlignmentLeft = false;
+ IsUpDownAlignmentBoth = false;
+ }
+
+ ///
+ /// This method calculate the up icon rectF position.
+ ///
+ void UpdateUpIconRectF()
+ {
+ if (IsUpDownVerticalAlignment)
+ {
+ _upIconRectF.X = _downIconRectF.X;
+ _upIconRectF.Y = (float)(_downIconRectF.Y + IconSize / 2 + DefaultAssistiveLabelPadding);
+ }
+ else
+ {
+ if (!(IsUpDownAlignmentBoth || IsUpDownAlignmentLeft))
+ {
+ _upIconRectF.X = (_downIconRectF.Width != 0 && ShowUpDownButton) ? this.IsRTL ? (_downIconRectF.X + IconSize) : _downIconRectF.X - IconSize : _downIconRectF.X;
+ }
+ _upIconRectF.Y = _downIconRectF.Y;
+ HandleUpIconInlinePosition();
+ }
+ _upIconRectF.Width = (ShowUpDownButton) ? UpDownButtonSize : 0;
+ _upIconRectF.Height = (ShowUpDownButton) ? UpDownButtonSize : 0;
+ }
+
+ ///
+ /// Handles the positioning of the up icon inline with other elements.
+ /// Adjusts the X-coordinate of the up icon based on alignment settings and RTL layout.
+ ///
+ void HandleUpIconInlinePosition()
+ {
+ if (IsUpDownAlignmentLeft)
+ {
+ _downIconRectF.X = (_upIconRectF.Width != 0 && ShowUpDownButton) ? !IsRTL ?
+(_upIconRectF.X + IconSize) : _upIconRectF.X - IconSize : _upIconRectF.X;
+ }
+ else if (IsUpDownAlignmentBoth)
+ {
+ _downIconRectF.X = !IsRTL ? (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding)
+: (float)(_trailViewWidth + (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+ }
+
+ ///
+ /// This method calculate the clear icon rectF position.
+ ///
+ bool IsClearIconVisible
+ {
+ get { return (ShowClearButton && IsLayoutFocused && !string.IsNullOrEmpty(Text)); }
+ }
+
+ ///
+ /// This method calculate the clear icon rectF position.
+ ///
+ void UpdateClearIconRectF()
+ {
+ _clearIconRectF.X = (_downIconRectF.Width != 0) ? GetClearIconX() : IsRTL ? _upIconRectF.X - IconSize : _upIconRectF.X;
+ _clearIconRectF.Y = IsUpDownVerticalAlignment ? (float)(_downIconRectF.Y + (DefaultAssistiveLabelPadding / 2) + (IconSize / 2) - (IconSize / 4)) : _downIconRectF.Y;
+ _clearIconRectF.Width = IsClearIconVisible ? IconSize : 0;
+ _clearIconRectF.Height = IsClearIconVisible ? IconSize : 0;
+ if (_downIconRectF.Width != 0)
+ {
+ _clearIconRectF.X -= (float)(DefaultAssistiveLabelPadding);
+ }
+ }
+
+ ///
+ /// Updates clear icon position based on up down alignment.
+ ///
+ private float GetClearIconX()
+ {
+ if (IsUpDownVerticalAlignment && !IsRTL && IsUpDownAlignmentLeft && ShowUpDownButton)
+ {
+ return (float)(Width - _trailViewWidth - IconSize - DefaultAssistiveLabelPadding) ;
+ }
+ if (IsUpDownAlignmentLeft && ShowUpDownButton)
+ {
+ return !IsRTL ? (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding) : (float)(_leadViewWidth + (IsOutlined ? BaseLineMaxHeight : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+
+ if (IsUpDownVerticalAlignment && IsUpDownAlignmentLeft && IsRTL)
+ {
+ return (float)(_trailViewWidth + (IsOutlined ? BaseLineMaxHeight : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+ else if ((IsUpDownAlignmentBoth || IsUpDownVerticalAlignment))
+ {
+ return IsRTL ? _downIconRectF.X + _downIconRectF.Width : _downIconRectF.X - _downIconRectF.Width;
+ }
+ return !IsRTL ? _upIconRectF.X - IconSize : _upIconRectF.X + IconSize;
+ }
+
+ ///
+ /// Handles the positioning of the down icon.
+ /// Adjusts the X-coordinate of the down icon based on down button alignment settings.
+ ///
+ void HandleDownIconPositionRTL()
+ {
+ if (IsUpDownVerticalAlignment)
+ {
+ if (IsUpDownAlignmentLeft)
+ {
+ _downIconRectF.X = (float)(Width - _leadViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
+ }
+ else
+ {
+ _downIconRectF.X = (float)(_trailViewWidth + (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+ }
+ else
+ {
+ if (IsUpDownAlignmentLeft || IsUpDownAlignmentBoth)
+ {
+ _upIconRectF.X = (float)(Width - _leadViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
+ }
+ else
+ {
+ _downIconRectF.X = (float)(Width - _downIconRectF.X - _downIconRectF.Width);
+ }
+ }
+ }
+
+ ///
+ /// Handles the positioning of the down icon for Right-to-Left (RTL) layouts.
+ /// Adjusts the X-coordinate of the down icon based on down button alignment settings.
+ ///
+ void HandleDownIconPosition()
+ {
+ if (IsUpDownVerticalAlignment)
+ {
+ if (IsUpDownAlignmentLeft)
+ {
+ _downIconRectF.X = (float)(_leadViewWidth + (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+ else
+ {
+ _downIconRectF.X = (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
+ }
+ }
+ else
+ {
+ if (IsUpDownAlignmentLeft || IsUpDownAlignmentBoth)
+ {
+ _upIconRectF.X = (float)(_leadViewWidth + (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) + DefaultAssistiveLabelPadding);
+ }
+ }
+ }
+
+ ///
+ /// This method calculate the down rectF position.
+ ///
+ void UpdateDownIconRectF()
+ {
+ _downIconRectF.X = (float)(Width - _trailViewWidth - (IsOutlined ? BaseLineMaxHeight * 2 : BaseLineMaxHeight) - IconSize - DefaultAssistiveLabelPadding);
+ HandleDownIconPosition();
+ if (IsNone)
+ {
+ _downIconRectF.Y = (float)((Content.Y + (Content.Height / 2)) - (UpDownButtonSize / 2));
+ if (IsUpDownVerticalAlignment)
+ {
+ _downIconRectF.Y = (float)((Content.Y + (Content.Height / 2)) - (UpDownButtonSize / 2));
+ }
+ }
+ else if (IsOutlined)
+ {
+ _downIconRectF.Y = (float)((_outlineRectF.Center.Y) - (UpDownButtonSize / 2));
+ if (IsUpDownVerticalAlignment)
+ {
+ _downIconRectF.Y = (float)((_outlineRectF.Center.Y) - ((IconSize * 1.5) / 2) - (DefaultAssistiveLabelPadding / 2));
+ }
+ }
+ else
+ {
+ _downIconRectF.Y = (float)(((Height - AssistiveLabelPadding - TotalAssistiveTextHeight()) / 2) - (UpDownButtonSize / 2));
+ if (IsUpDownVerticalAlignment)
+ {
+ _downIconRectF.Y = (float)(((Height - AssistiveLabelPadding - TotalAssistiveTextHeight()) / 2) - ((IconSize * 1.5) / 2) - (DefaultAssistiveLabelPadding / 2));
+ }
+ }
+
+ _downIconRectF.Width = (IsPassowordToggleIconVisible || ShowUpDownButton) ? UpDownButtonSize : 0;
+ _downIconRectF.Height = (IsPassowordToggleIconVisible || ShowUpDownButton) ? UpDownButtonSize : 0;
+
+ if (IsRTL)
+ {
+ HandleDownIconPositionRTL();
+ }
+
}
///
@@ -647,6 +996,8 @@ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
UpdateIconRectF();
DrawBorder(canvas, dirtyRect);
DrawHintText(canvas, dirtyRect);
+ DrawClearIcon(canvas, _clearIconRectF);
+ DrawUpDownIcon(canvas, dirtyRect);
DrawAssistiveText(canvas, dirtyRect);
DrawPasswordToggleIcon(canvas, dirtyRect);
if (_effectsRenderer != null)
@@ -664,6 +1015,38 @@ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
canvas.ResetState();
}
+ ///
+ /// Draws a button on the canvas with the specified color and icon.
+ ///
+ /// The canvas to draw on.
+ /// The color of the button.
+ /// The rectangle defining the button's boundaries.
+ /// The action to draw the button's icon.
+ void DrawButton(ICanvas canvas, Color color, RectF iconRectF, Action drawAction)
+ {
+ canvas.SaveState();
+ canvas.FillColor = color;
+ canvas.StrokeColor = color;
+ canvas.StrokeSize = 1.5f;
+ drawAction.Invoke(canvas, iconRectF);
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Draws the up and down icons for the numeric input control.
+ ///
+ /// The canvas to draw on.
+ /// The rectangle defining the control's boundaries.
+ void DrawUpDownIcon(ICanvas canvas, RectF rectF)
+ {
+ if (!this.ShowUpDownButton || _tilRenderer is null)
+ {
+ return;
+ }
+ DrawButton(canvas, UpButtonColor, IsUpDownVerticalAlignment ? _downIconRectF : _upIconRectF, _tilRenderer.DrawUpButton);
+ DrawButton(canvas, DownButtonColor, IsUpDownVerticalAlignment ? _upIconRectF : _downIconRectF, _tilRenderer.DrawDownButton);
+ }
+
///
/// Changes the visual state of the control.
///
@@ -844,7 +1227,7 @@ async void ITouchListener.OnTouch(PointerEventArgs e)
///
/// private void OnPasswordVisibilityToggled(object sender, PasswordVisibilityToggledEventArgs e)
/// {
- /// var passwordVisbility = e.IsPasswordVisible;
+ /// var passwordVisibility = e.IsPasswordVisible;
/// }
/// ]]>
///
diff --git a/maui/src/Themes/SfNumericEntryStyles.xaml b/maui/src/Themes/SfNumericEntryStyles.xaml
new file mode 100644
index 00000000..c8b18ff1
--- /dev/null
+++ b/maui/src/Themes/SfNumericEntryStyles.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfNumericEntryStyles.xaml.cs b/maui/src/Themes/SfNumericEntryStyles.xaml.cs
new file mode 100644
index 00000000..3ade57ce
--- /dev/null
+++ b/maui/src/Themes/SfNumericEntryStyles.xaml.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui.Controls;
+
+namespace Syncfusion.Maui.Toolkit.NumericEntry;
+
+///
+/// Initializing SfNumericEntryStyles class
+///
+public partial class SfNumericEntryStyles : ResourceDictionary
+{
+ ///
+ /// Initializes a new instance of the SfNumericEntryStyles class.
+ ///
+ public SfNumericEntryStyles()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfNumericUpDownStyles.xaml b/maui/src/Themes/SfNumericUpDownStyles.xaml
new file mode 100644
index 00000000..e9fa2bff
--- /dev/null
+++ b/maui/src/Themes/SfNumericUpDownStyles.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfNumericUpDownStyles.xaml.cs b/maui/src/Themes/SfNumericUpDownStyles.xaml.cs
new file mode 100644
index 00000000..d087a0f1
--- /dev/null
+++ b/maui/src/Themes/SfNumericUpDownStyles.xaml.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui.Controls;
+
+namespace Syncfusion.Maui.Toolkit.NumericUpDown;
+
+///
+/// Initializes the SfNumericUpDownStyles class.
+///
+public partial class SfNumericUpDownStyles : ResourceDictionary
+{
+ ///
+ /// Initializes a new instance of the SfNumericUpDownStyles class.
+ ///
+ public SfNumericUpDownStyles()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file