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