diff --git a/src/Compatibility/Core/src/Android/Renderers/SearchBarRenderer.cs b/src/Compatibility/Core/src/Android/Renderers/SearchBarRenderer.cs index 2ff7e189e822..2d264406a515 100644 --- a/src/Compatibility/Core/src/Android/Renderers/SearchBarRenderer.cs +++ b/src/Compatibility/Core/src/Android/Renderers/SearchBarRenderer.cs @@ -264,6 +264,7 @@ void UpdateText() Control.SetQuery(text, false); } + [PortHandler] void UpdateCharacterSpacing() { if (!Forms.IsLollipopOrNewer) diff --git a/src/Compatibility/Core/src/iOS/Renderers/SearchBarRenderer.cs b/src/Compatibility/Core/src/iOS/Renderers/SearchBarRenderer.cs index c07253a9f49b..10c46ddeb4d4 100644 --- a/src/Compatibility/Core/src/iOS/Renderers/SearchBarRenderer.cs +++ b/src/Compatibility/Core/src/iOS/Renderers/SearchBarRenderer.cs @@ -226,6 +226,7 @@ void OnTextChanged(object sender, UISearchBarTextChangedEventArgs a) UpdateOnTextChanged(); } + [PortHandler("The code related to Placeholder remains to be ported")] void UpdateCharacterSpacing() { _textField = _textField ?? Control.FindDescendantView(); diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index 2ba443aee005..2e75f049acef 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -36,7 +36,6 @@ void SetupMauiLayout() var horizontalStack = new HorizontalStackLayout() { Spacing = 2, BackgroundColor = Color.CornflowerBlue }; verticalStack.Add(new Label { Text = " ", Padding = new Thickness(10) }); - var label = new Label { Text = "End-aligned text", BackgroundColor = Color.Fuchsia, HorizontalTextAlignment = TextAlignment.End }; label.Margin = new Thickness(15, 10, 20, 15); @@ -105,6 +104,11 @@ void SetupMauiLayout() verticalStack.Add(new ProgressBar { Progress = 0.5, BackgroundColor = Color.LightCoral }); verticalStack.Add(new ProgressBar { Progress = 0.5, ProgressColor = Color.Purple }); + var searchBar = new SearchBar(); + searchBar.CharacterSpacing = 4; + searchBar.Text = "A search query"; + verticalStack.Add(searchBar); + var searchBar = new SearchBar(); searchBar.Text = "A search query"; verticalStack.Add(searchBar); diff --git a/src/Core/src/Core/ISearchBar.cs b/src/Core/src/Core/ISearchBar.cs index c57966a82810..df1d4a2d7f4c 100644 --- a/src/Core/src/Core/ISearchBar.cs +++ b/src/Core/src/Core/ISearchBar.cs @@ -9,5 +9,10 @@ public interface ISearchBar : IView, IPlaceholder, ITextAlignment /// Gets a string containing the query text in the SearchBar. /// string Text { get; } + + /// + /// Gets a string containing the query text in the SearchBar. + /// + double CharacterSpacing { get; } } } \ No newline at end of file diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs index c9b68c068d02..6a70878c9244 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs @@ -27,10 +27,15 @@ public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar { handler.TypedNativeView?.UpdatePlaceholder(searchBar); } - + public static void MapHorizontalTextAlignment(SearchBarHandler handler, ISearchBar searchBar) { handler.QueryEditor?.UpdateHorizontalTextAlignment(searchBar); } + + public static void MapCharacterSpacing(SearchBarHandler handler, ISearchBar searchBar) + { + handler.QueryEditor?.UpdateCharacterSpacing(searchBar); + } } } \ No newline at end of file diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Standard.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Standard.cs index c86c4999a5e3..1cfa95895de7 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Standard.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Standard.cs @@ -9,5 +9,6 @@ public partial class SearchBarHandler : AbstractViewHandler public static void MapText(IViewHandler handler, ISearchBar searchBar) { } public static void MapPlaceholder(IViewHandler handler, ISearchBar searchBar) { } public static void MapHorizontalTextAlignment(IViewHandler handler, ISearchBar searchBar) { } + public static void MapCharacterSpacing(IViewHandler handler, ISearchBar searchBar) { } } } \ No newline at end of file diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs index 70a65304dda0..a5599a757ef1 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs @@ -6,7 +6,8 @@ public partial class SearchBarHandler { [nameof(ISearchBar.Text)] = MapText, [nameof(ISearchBar.Placeholder)] = MapPlaceholder, - [nameof(ISearchBar.HorizontalTextAlignment)] = MapHorizontalTextAlignment + [nameof(ISearchBar.HorizontalTextAlignment)] = MapHorizontalTextAlignment, + [nameof(ISearchBar.CharacterSpacing)] = MapCharacterSpacing }; public SearchBarHandler() : base(SearchBarMapper) diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs index 52ace8e55ef5..fc50d380f966 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs @@ -1,16 +1,18 @@ +using System.Drawing; using UIKit; namespace Microsoft.Maui.Handlers { public partial class SearchBarHandler : AbstractViewHandler { - UITextField? _textField; + UITextField? _editor; + public UITextField? QueryEditor => _editor; protected override UISearchBar CreateNativeView() { - var searchBar = new UISearchBar(); + var searchBar = new UISearchBar(RectangleF.Empty) { ShowsCancelButton = true, BarStyle = UIBarStyle.Default }; - _textField = searchBar.FindDescendantView(); + _editor = searchBar.FindDescendantView(); return searchBar; } @@ -27,7 +29,12 @@ public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar public static void MapHorizontalTextAlignment(SearchBarHandler handler, ISearchBar searchBar) { - handler.TypedNativeView?.UpdateHorizontalTextAlignment(searchBar, handler._textField); + handler.QueryEditor?.UpdateHorizontalTextAlignment(searchBar); + } + + public static void MapCharacterSpacing(SearchBarHandler handler, ISearchBar searchBar) + { + handler.QueryEditor?.UpdateCharacterSpacing(searchBar); } } } \ No newline at end of file diff --git a/src/Core/src/Platform/Android/TextViewExtensions.cs b/src/Core/src/Platform/Android/TextViewExtensions.cs index 466017d1ec1d..138a415359ca 100644 --- a/src/Core/src/Platform/Android/TextViewExtensions.cs +++ b/src/Core/src/Platform/Android/TextViewExtensions.cs @@ -29,6 +29,9 @@ public static void UpdateTextColor(this TextView textView, ILabel label, Color d public static void UpdateCharacterSpacing(this TextView textView, ILabel label) => textView.LetterSpacing = label.CharacterSpacing.ToEm(); + public static void UpdateCharacterSpacing(this TextView textView, ISearchBar searchBar) => + textView.LetterSpacing = searchBar.CharacterSpacing.ToEm(); + public static void UpdateFont(this TextView textView, ILabel label, IFontManager fontManager) { var font = label.Font; diff --git a/src/Core/src/Platform/Android/UnitExtensions.cs b/src/Core/src/Platform/Android/UnitExtensions.cs index 7c73aa816dfd..6cbd12bece8d 100644 --- a/src/Core/src/Platform/Android/UnitExtensions.cs +++ b/src/Core/src/Platform/Android/UnitExtensions.cs @@ -3,7 +3,7 @@ [PortHandler] public static class UnitExtensions { - public static float EmCoefficient = 0.0624f; + public const float EmCoefficient = 0.0624f; public static float ToEm(this double pt) { diff --git a/src/Core/src/Platform/iOS/EntryExtensions.cs b/src/Core/src/Platform/iOS/EntryExtensions.cs index 861c08eaf577..7013d7766391 100644 --- a/src/Core/src/Platform/iOS/EntryExtensions.cs +++ b/src/Core/src/Platform/iOS/EntryExtensions.cs @@ -38,12 +38,12 @@ public static void UpdateIsPassword(this UITextField textField, IEntry entry) textField.SecureTextEntry = entry.IsPassword; } - public static void UpdateHorizontalTextAlignment(this UITextField textField, IEntry entry) + public static void UpdateHorizontalTextAlignment(this UITextField textField, ITextAlignment textAlignment) { // We don't have a FlowDirection yet, so there's nothing to pass in here. // TODO: Update this when FlowDirection is available // (or update the extension to take an ILabel instead of an alignment and work it out from there) - textField.TextAlignment = entry.HorizontalTextAlignment.ToNative(true); + textField.TextAlignment = textAlignment.HorizontalTextAlignment.ToNative(true); } public static void UpdateIsTextPredictionEnabled(this UITextField textField, IEntry entry) diff --git a/src/Core/src/Platform/iOS/SearchBarExtensions.cs b/src/Core/src/Platform/iOS/SearchBarExtensions.cs index 33d412a95131..097adce5401a 100644 --- a/src/Core/src/Platform/iOS/SearchBarExtensions.cs +++ b/src/Core/src/Platform/iOS/SearchBarExtensions.cs @@ -13,23 +13,5 @@ public static void UpdatePlaceholder(this UISearchBar uiSearchBar, ISearchBar se { uiSearchBar.Placeholder = searchBar.Placeholder; } - - public static void UpdateHorizontalTextAlignment(this UISearchBar uiSearchBar, ISearchBar searchBar) - { - UpdateHorizontalTextAlignment(uiSearchBar, searchBar, null); - } - - public static void UpdateHorizontalTextAlignment(this UISearchBar uiSearchBar, ISearchBar searchBar, UITextField? textField) - { - textField ??= uiSearchBar.FindDescendantView(); - - if (textField == null) - return; - - // We don't have a FlowDirection yet, so there's nothing to pass in here. - // TODO: Update this when FlowDirection is available - // (or update the extension to take an ILabel instead of an alignment and work it out from there) - textField.TextAlignment = searchBar.HorizontalTextAlignment.ToNative(true); - } } } \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/TextFieldExtensions.cs b/src/Core/src/Platform/iOS/TextFieldExtensions.cs new file mode 100644 index 000000000000..a5b19126346c --- /dev/null +++ b/src/Core/src/Platform/iOS/TextFieldExtensions.cs @@ -0,0 +1,17 @@ +using UIKit; + +namespace Microsoft.Maui +{ + public static class TextFieldExtensions + { + public static void UpdateCharacterSpacing(this UITextField textField, ISearchBar searchBar) + { + var textAttr = textField.AttributedText?.AddCharacterSpacing(searchBar.Text, searchBar.CharacterSpacing); + + if (textAttr != null) + textField.AttributedText = textAttr; + + // TODO: Include AttributedText to Label Placeholder + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs index 6267f1edf7a0..96150ec22dd6 100644 --- a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs @@ -181,7 +181,7 @@ Task ValidateNativeBackgroundColor(ILabel label, Color color) } double GetNativeCharacterSpacing(LabelHandler labelHandler) => - Math.Round(GetNativeLabel(labelHandler).LetterSpacing / UnitExtensions.EmCoefficient, 4); + Math.Round(GetNativeLabel(labelHandler).LetterSpacing / UnitExtensions.EmCoefficient, EmCoefficientPrecision); TextUtils.TruncateAt GetNativeLineBreakMode(LabelHandler labelHandler) => GetNativeLabel(labelHandler).Ellipsize; diff --git a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.iOS.cs index 000c8e14422b..bcbf38e3c744 100644 --- a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.iOS.cs @@ -143,18 +143,7 @@ double GetNativeCharacterSpacing(LabelHandler labelHandler) { var nativeLabel = GetNativeLabel(labelHandler); var text = nativeLabel.AttributedText; - if (text == null) - return 0; - - var value = text.GetAttribute(UIStringAttributeKey.KerningAdjustment, 0, out var range); - if (value == null) - return 0; - - Assert.Equal(0, range.Location); - Assert.Equal(text.Length, range.Length); - - var kerning = Assert.IsType(value); - return kerning.DoubleValue; + return text.GetCharacterSpacing(); } UITextAlignment GetNativeTextAlignment(LabelHandler labelHandler) => diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs index 8f45a2b0938f..a3c56622d60e 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs @@ -1,8 +1,8 @@ -using System.Linq; -using System.Threading.Tasks; -using Android.Widget; +using Android.Widget; using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Handlers; +using System.Linq; +using System.Threading.Tasks; using Xunit; using SearchView = AndroidX.AppCompat.Widget.SearchView; @@ -36,6 +36,32 @@ public async Task HorizontalTextAlignmentInitializesCorrectly() values.NativeViewValue.AssertHasFlag(expectedValue); } + [Fact(DisplayName = "CharacterSpacing Initializes Correctly")] + public async Task CharacterSpacingInitializesCorrectly() + { + var xplatCharacterSpacing = 4; + + var searchBar = new SearchBarStub() + { + CharacterSpacing = xplatCharacterSpacing, + Text = "Test" + }; + + float expectedValue = searchBar.CharacterSpacing.ToEm(); + + var values = await GetValueAsync(searchBar, (handler) => + { + return new + { + ViewValue = searchBar.CharacterSpacing, + NativeViewValue = GetNativeCharacterSpacing(handler) + }; + }); + + Assert.Equal(xplatCharacterSpacing, values.ViewValue); + Assert.Equal(expectedValue, values.NativeViewValue, EmCoefficientPrecision); + } + SearchView GetNativeSearchBar(SearchBarHandler searchBarHandler) => (SearchView)searchBarHandler.View; @@ -45,14 +71,23 @@ string GetNativeText(SearchBarHandler searchBarHandler) => string GetNativePlaceholder(SearchBarHandler searchBarHandler) => GetNativeSearchBar(searchBarHandler).QueryHint; - Android.Views.TextAlignment GetNativeTextAlignment(SearchBarHandler searchBarHandler) + double GetNativeCharacterSpacing(SearchBarHandler searchBarHandler) { var searchView = GetNativeSearchBar(searchBarHandler); var editText = searchView.GetChildrenOfType().FirstOrDefault(); - if (editText == null) - return Android.Views.TextAlignment.Inherit; + if (editText != null) + { + return editText.LetterSpacing; + } + return -1; + } + + Android.Views.TextAlignment GetNativeTextAlignment(SearchBarHandler searchBarHandler) + { + var searchView = GetNativeSearchBar(searchBarHandler); + var editText = searchView.GetChildrenOfType().First(); return editText.TextAlignment; } } diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs index 6c2f8b8e7354..be9fcddf885f 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs @@ -57,4 +57,4 @@ public async Task PlaceholderInitializesCorrectly() await ValidatePropertyInitValue(searchBar, () => searchBar.Placeholder, GetNativePlaceholder, searchBar.Placeholder); } } -} +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.iOS.cs index 2b3e59f18929..6b2a5bd2db08 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.iOS.cs @@ -33,6 +33,31 @@ public async Task HorizontalTextAlignmentInitializesCorrectly() Assert.Equal(xplatHorizontalTextAlignment, values.ViewValue); values.NativeViewValue.AssertHasFlag(expectedValue); } + + [Fact(DisplayName = "CharacterSpacing Initializes Correctly")] + public async Task CharacterSpacingInitializesCorrectly() + { + string originalText = "Test"; + var xplatCharacterSpacing = 4; + + var slider = new SearchBarStub() + { + CharacterSpacing = xplatCharacterSpacing, + Text = originalText + }; + + var values = await GetValueAsync(slider, (handler) => + { + return new + { + ViewValue = slider.CharacterSpacing, + NativeViewValue = GetNativeCharacterSpacing(handler) + }; + }); + + Assert.Equal(xplatCharacterSpacing, values.ViewValue); + Assert.Equal(xplatCharacterSpacing, values.NativeViewValue); + } UISearchBar GetNativeSearchBar(SearchBarHandler searchBarHandler) => (UISearchBar)searchBarHandler.View; @@ -53,5 +78,13 @@ UITextAlignment GetNativeTextAlignment(SearchBarHandler searchBarHandler) return textField.TextAlignment; } + + double GetNativeCharacterSpacing(SearchBarHandler searchBarHandler) + { + var searchBar = GetNativeSearchBar(searchBarHandler); + var textField = searchBar.FindDescendantView(); + + return textField.AttributedText.GetCharacterSpacing(); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/SearchBarStub.cs b/src/Core/tests/DeviceTests/Stubs/SearchBarStub.cs index 4f8cf15957b1..70a6b7f824c6 100644 --- a/src/Core/tests/DeviceTests/Stubs/SearchBarStub.cs +++ b/src/Core/tests/DeviceTests/Stubs/SearchBarStub.cs @@ -9,5 +9,7 @@ public partial class SearchBarStub : StubBase, ISearchBar public string Placeholder { get; set; } public TextAlignment HorizontalTextAlignment { get; set; } + + public double CharacterSpacing { get; set; } } } \ No newline at end of file