From 17e2a4b94d5105b53c5875d9416d043f14313305 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 22 Mar 2016 17:16:53 -0600 Subject: Add options for specifying layout of button text/image content Also make the layout and layout defaults consistent across platforms --- .../Xamarin.Forms.ControlGallery.WP8.csproj | 1 + Xamarin.Forms.ControlGallery.WP8/coffee.png | Bin 0 -> 490 bytes .../Xamarin.Forms.ControlGallery.Windows.csproj | 1 + Xamarin.Forms.ControlGallery.Windows/coffee.png | Bin 0 -> 490 bytes ...amarin.Forms.ControlGallery.WindowsPhone.csproj | 1 + .../coffee.png | Bin 0 -> 490 bytes ...in.Forms.ControlGallery.WindowsUniversal.csproj | 1 + .../coffee.png | Bin 0 -> 490 bytes .../Bugzilla27417.cs | 59 ++++++++++ .../Bugzilla27417Xaml.xaml | 15 +++ .../Bugzilla27417Xaml.xaml.cs | 20 ++++ .../Xamarin.Forms.Controls.Issues.Shared.projitems | 11 ++ Xamarin.Forms.Controls/App.cs | 7 +- Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs | 24 ++++ Xamarin.Forms.Core/Button.cs | 80 +++++++++++++ .../AppCompat/ButtonRenderer.cs | 49 ++++++-- .../Renderers/ButtonRenderer.cs | 52 +++++++-- Xamarin.Forms.Platform.WP8/ButtonRenderer.cs | 76 +++++++++++-- Xamarin.Forms.Platform.WinRT/ButtonRenderer.cs | 79 ++++++++++--- .../Renderers/ButtonRenderer.cs | 126 +++++++++++++++++---- .../Button+ButtonContentLayout+ImagePosition.xml | 73 ++++++++++++ .../Xamarin.Forms/Button+ButtonContentLayout.xml | 93 +++++++++++++++ .../Button+ButtonContentTypeConverter.xml | 51 +++++++++ docs/Xamarin.Forms.Core/Xamarin.Forms/Button.xml | 31 +++++ 24 files changed, 785 insertions(+), 65 deletions(-) create mode 100644 Xamarin.Forms.ControlGallery.WP8/coffee.png create mode 100644 Xamarin.Forms.ControlGallery.Windows/coffee.png create mode 100644 Xamarin.Forms.ControlGallery.WindowsPhone/coffee.png create mode 100644 Xamarin.Forms.ControlGallery.WindowsUniversal/coffee.png create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417.cs create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml.cs create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout+ImagePosition.xml create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout.xml create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentTypeConverter.xml diff --git a/Xamarin.Forms.ControlGallery.WP8/Xamarin.Forms.ControlGallery.WP8.csproj b/Xamarin.Forms.ControlGallery.WP8/Xamarin.Forms.ControlGallery.WP8.csproj index f6728fa2..99ca9e5f 100644 --- a/Xamarin.Forms.ControlGallery.WP8/Xamarin.Forms.ControlGallery.WP8.csproj +++ b/Xamarin.Forms.ControlGallery.WP8/Xamarin.Forms.ControlGallery.WP8.csproj @@ -180,6 +180,7 @@ PreserveNewest + PreserveNewest diff --git a/Xamarin.Forms.ControlGallery.WP8/coffee.png b/Xamarin.Forms.ControlGallery.WP8/coffee.png new file mode 100644 index 00000000..350257c0 Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WP8/coffee.png differ diff --git a/Xamarin.Forms.ControlGallery.Windows/Xamarin.Forms.ControlGallery.Windows.csproj b/Xamarin.Forms.ControlGallery.Windows/Xamarin.Forms.ControlGallery.Windows.csproj index c5f558b5..c977cece 100644 --- a/Xamarin.Forms.ControlGallery.Windows/Xamarin.Forms.ControlGallery.Windows.csproj +++ b/Xamarin.Forms.ControlGallery.Windows/Xamarin.Forms.ControlGallery.Windows.csproj @@ -177,6 +177,7 @@ + diff --git a/Xamarin.Forms.ControlGallery.Windows/coffee.png b/Xamarin.Forms.ControlGallery.Windows/coffee.png new file mode 100644 index 00000000..350257c0 Binary files /dev/null and b/Xamarin.Forms.ControlGallery.Windows/coffee.png differ diff --git a/Xamarin.Forms.ControlGallery.WindowsPhone/Xamarin.Forms.ControlGallery.WindowsPhone.csproj b/Xamarin.Forms.ControlGallery.WindowsPhone/Xamarin.Forms.ControlGallery.WindowsPhone.csproj index 50c990ce..d97a36de 100644 --- a/Xamarin.Forms.ControlGallery.WindowsPhone/Xamarin.Forms.ControlGallery.WindowsPhone.csproj +++ b/Xamarin.Forms.ControlGallery.WindowsPhone/Xamarin.Forms.ControlGallery.WindowsPhone.csproj @@ -154,6 +154,7 @@ + diff --git a/Xamarin.Forms.ControlGallery.WindowsPhone/coffee.png b/Xamarin.Forms.ControlGallery.WindowsPhone/coffee.png new file mode 100644 index 00000000..350257c0 Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WindowsPhone/coffee.png differ diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj index 00616897..d0c196e2 100644 --- a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj @@ -110,6 +110,7 @@ + diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/coffee.png b/Xamarin.Forms.ControlGallery.WindowsUniversal/coffee.png new file mode 100644 index 00000000..350257c0 Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WindowsUniversal/coffee.png differ diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417.cs new file mode 100644 index 00000000..4f01a63d --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417.cs @@ -0,0 +1,59 @@ +using Xamarin.Forms.CustomAttributes; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 27417, + "Button.Image behaviors differently on each platform and has extra padding even with no Text", PlatformAffected.All)] + public class Bugzilla27417 : TestContentPage + { + protected override void Init() + { + var instructions = new Label { Text = @"There should be 6 buttons below. +The first button should have the text 'Click Me' in the center. +The second button should have an image in the center and no text. +The third button should have the image on the left and the text on the right. +The fourth button should have the image on the top and the text on the bottom. +The fifth button should have the image on the right and the text on the left. +The sixth button should have the image on the bottom and the text on the top." }; + + Content = new StackLayout + { + Spacing = 10, + Children = + { + instructions, + new ScrollView + { + Content = new StackLayout + { + Spacing = 10, + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center, + Children = + { + new Button { Text = "Click Me", BackgroundColor = Color.Gray }, + new Button { Image = "coffee.png", BackgroundColor = Color.Gray }, + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Left, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Top, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Bottom, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 10)) + } + } + } + } + }; + } + + static Button CreateButton(Button.ButtonContentLayout layout) + { + return new Button + { + Text = "Click Me", + Image = "coffee.png", + ContentLayout = layout, + BackgroundColor = Color.Gray + }; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml new file mode 100644 index 00000000..ab652b30 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml.cs new file mode 100644 index 00000000..96d2ac45 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla27417Xaml.xaml.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Controls.Issues +{ + public partial class Bugzilla27417Xaml : ContentPage + { + public Bugzilla27417Xaml () + { +#if APP + InitializeComponent (); +#endif + } + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 0a2d251b..6ae60c78 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -24,6 +24,11 @@ + + + Bugzilla27417Xaml.xaml + Code + @@ -485,4 +490,10 @@ MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/App.cs b/Xamarin.Forms.Controls/App.cs index 2556bc8c..d75b35af 100644 --- a/Xamarin.Forms.Controls/App.cs +++ b/Xamarin.Forms.Controls/App.cs @@ -10,7 +10,7 @@ namespace Xamarin.Forms.Controls { public const string AppName = "XamarinFormsControls"; static string s_insightsKey; - + // ReSharper disable once InconsistentNaming public static int IOSVersion = -1; @@ -24,8 +24,7 @@ namespace Xamarin.Forms.Controls _testCloudService = DependencyService.Get(); InitInsights(); // MainPage = new MainPageLifeCycleTests (); - MainPage = new MasterDetailPage - { + MainPage = new MasterDetailPage { Master = new ContentPage { Title = "Master", BackgroundColor = Color.Red }, Detail = CoreGallery.GetMainPage() }; @@ -65,7 +64,7 @@ namespace Xamarin.Forms.Controls static Assembly GetAssembly(out string assemblystring) { - assemblystring = typeof (App).AssemblyQualifiedName.Split(',')[1].Trim(); + assemblystring = typeof(App).AssemblyQualifiedName.Split(',')[1].Trim(); var assemblyname = new AssemblyName(assemblystring); return Assembly.Load(assemblyname); } diff --git a/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs b/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs index 29ad2d0e..78d97a86 100644 --- a/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs +++ b/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; namespace Xamarin.Forms.Core.UnitTests @@ -177,5 +178,28 @@ namespace Xamarin.Forms.Core.UnitTests Assert.True (button.IsEnabled); } + + [Test] + public void ButtonContentLayoutTypeConverterTest() + { + var converter = new Button.ButtonContentTypeConverter(); + Assert.True(converter.CanConvertFrom(typeof(string))); + + AssertButtonContentLayoutsEqual(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Left, 10), converter.ConvertFromInvariantString("left,10")); + AssertButtonContentLayoutsEqual(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 10), converter.ConvertFromInvariantString("right")); + AssertButtonContentLayoutsEqual(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Top, 20), converter.ConvertFromInvariantString("top,20")); + AssertButtonContentLayoutsEqual(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Left, 15), converter.ConvertFromInvariantString("15")); + AssertButtonContentLayoutsEqual(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Bottom, 0), converter.ConvertFromInvariantString("Bottom, 0")); + + Assert.Throws(() => converter.ConvertFromInvariantString("")); + } + + private void AssertButtonContentLayoutsEqual(Button.ButtonContentLayout layout1, object layout2) + { + var bcl = (Button.ButtonContentLayout)layout2; + + Assert.AreEqual(layout1.Position, bcl.Position); + Assert.AreEqual(layout1.Spacing, bcl.Spacing); + } } } diff --git a/Xamarin.Forms.Core/Button.cs b/Xamarin.Forms.Core/Button.cs index 4662105f..9c746c9e 100644 --- a/Xamarin.Forms.Core/Button.cs +++ b/Xamarin.Forms.Core/Button.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Globalization; using System.Windows.Input; using Xamarin.Forms.Platform; @@ -12,6 +14,9 @@ namespace Xamarin.Forms public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(Button), null, propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).CommandCanExecuteChanged(bindable, EventArgs.Empty)); + public static readonly BindableProperty ContentLayoutProperty = + BindableProperty.Create("ContentLayout", typeof(ButtonContentLayout), typeof(Button), new ButtonContentLayout(ButtonContentLayout.ImagePosition.Left, DefaultSpacing)); + public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Button), null, propertyChanged: (bindable, oldVal, newVal) => ((Button)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged)); @@ -39,6 +44,8 @@ namespace Xamarin.Forms bool _cancelEvents; + const double DefaultSpacing = 10; + public Color BorderColor { get { return (Color)GetValue(BorderColorProperty); } @@ -57,6 +64,12 @@ namespace Xamarin.Forms set { SetValue(BorderWidthProperty, value); } } + public ButtonContentLayout ContentLayout + { + get { return (ButtonContentLayout)GetValue(ContentLayoutProperty); } + set { SetValue(ContentLayoutProperty, value); } + } + public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } @@ -247,5 +260,72 @@ namespace Xamarin.Forms button._cancelEvents = false; } + + [DebuggerDisplay("Image Position = {Position}, Spacing = {Spacing}")] + [TypeConverter(typeof(ButtonContentTypeConverter))] + public sealed class ButtonContentLayout + { + public enum ImagePosition + { + Left, + Top, + Right, + Bottom + } + + public ButtonContentLayout(ImagePosition position, double spacing) + { + Position = position; + Spacing = spacing; + } + + public ImagePosition Position { get; } + + public double Spacing { get; } + + public override string ToString() + { + return $"Image Position = {Position}, Spacing = {Spacing}"; + } + } + + public sealed class ButtonContentTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value == null) + { + throw new InvalidOperationException($"Cannot convert null into {typeof(ButtonContentLayout)}"); + } + + string[] parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 1 && parts.Length != 2) + { + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(ButtonContentLayout)}"); + } + + double spacing = DefaultSpacing; + var position = ButtonContentLayout.ImagePosition.Left; + + var spacingFirst = char.IsDigit(parts[0][0]); + + int positionIndex = spacingFirst ? (parts.Length == 2 ? 1 : -1) : 0; + int spacingIndex = spacingFirst ? 0 : (parts.Length == 2 ? 1 : -1); + + if (spacingIndex > -1) + { + spacing = double.Parse(parts[spacingIndex]); + } + + if (positionIndex > -1) + { + position = + (ButtonContentLayout.ImagePosition)Enum.Parse(typeof(ButtonContentLayout.ImagePosition), parts[positionIndex], true); + } + + return new ButtonContentLayout(position, spacing); + } + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs index c7df08e2..7b7503e0 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs @@ -158,16 +158,51 @@ namespace Xamarin.Forms.Platform.Android.AppCompat void UpdateBitmap() { - FileImageSource elementImage = Element.Image; - string imageFile = elementImage?.File; - if (elementImage != null && !string.IsNullOrEmpty(imageFile)) + var elementImage = Element.Image; + var imageFile = elementImage?.File; + + if (elementImage == null || string.IsNullOrEmpty(imageFile)) + { + Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + return; + } + + var image = Context.Resources.GetDrawable(imageFile); + + if (string.IsNullOrEmpty(Element.Text)) { - Drawable image = Context.Resources.GetDrawable(imageFile); - Control.SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null); + // No text, so no need for relative position; just center the image + // There's no option for just plain-old centering, so we'll use Top + // (which handles the horizontal centering) and some tricksy padding + // to handle the vertical centering + Control.SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + Control.SetPadding(0, Control.PaddingTop, 0, -Control.PaddingTop); image?.Dispose(); + return; } - else - Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + + var layout = Element.ContentLayout; + + Control.CompoundDrawablePadding = (int)layout.Spacing; + + switch (layout.Position) + { + case Button.ButtonContentLayout.ImagePosition.Top: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + break; + case Button.ButtonContentLayout.ImagePosition.Bottom: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, image); + break; + case Button.ButtonContentLayout.ImagePosition.Right: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, image, null); + break; + default: + // Defaults to image on the left + Control.SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null); + break; + } + + image?.Dispose(); } void UpdateEnabled() diff --git a/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs index eb9b884f..0d93207a 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs @@ -138,17 +138,53 @@ namespace Xamarin.Forms.Platform.Android UpdateDrawable(); } - async void UpdateBitmap() + void UpdateBitmap() { - if (Element.Image != null && !string.IsNullOrEmpty(Element.Image.File)) + var elementImage = Element.Image; + var imageFile = elementImage?.File; + + if (elementImage == null || string.IsNullOrEmpty(imageFile)) { - Drawable image = Context.Resources.GetDrawable(Element.Image.File); - Control.SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null); - if (image != null) - image.Dispose(); - } - else Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + return; + } + + var image = Context.Resources.GetDrawable(imageFile); + + if (string.IsNullOrEmpty(Element.Text)) + { + // No text, so no need for relative position; just center the image + // There's no option for just plain-old centering, so we'll use Top + // (which handles the horizontal centering) and some tricksy padding + // to handle the vertical centering + Control.SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + Control.SetPadding(0, Control.PaddingTop, 0, -Control.PaddingTop); + image?.Dispose(); + return; + } + + var layout = Element.ContentLayout; + + Control.CompoundDrawablePadding = (int)layout.Spacing; + + switch (layout.Position) + { + case Button.ButtonContentLayout.ImagePosition.Top: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + break; + case Button.ButtonContentLayout.ImagePosition.Bottom: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, null, image); + break; + case Button.ButtonContentLayout.ImagePosition.Right: + Control.SetCompoundDrawablesWithIntrinsicBounds(null, null, image, null); + break; + default: + // Defaults to image on the left + Control.SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null); + break; + } + + image?.Dispose(); } void UpdateDrawable() diff --git a/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs b/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs index 886a7768..a75173b6 100644 --- a/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.WP8/ButtonRenderer.cs @@ -81,20 +81,72 @@ namespace Xamarin.Forms.Platform.WinPhone void UpdateContent() { - if (Element.Image != null) + var text = Element.Text; + var elementImage = Element.Image; + + // No image, just the text + if (elementImage == null) + { + Control.Content = text; + return; + } + + var image = new WImage + { + Source = new BitmapImage(new Uri("/" + elementImage.File, UriKind.Relative)), + Width = 30, + Height = 30, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; + + // No text, just the image + if (string.IsNullOrEmpty(text)) + { + Control.Content = image; + return; + } + + // Both image and text, so we need to build a container for them + var layout = Element.ContentLayout; + var container = new StackPanel(); + var textBlock = new TextBlock + { + Text = text, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; + + var spacing = layout.Spacing; + + container.HorizontalAlignment = HorizontalAlignment.Center; + container.VerticalAlignment = VerticalAlignment.Center; + + switch (layout.Position) { - Control.Content = new StackPanel - { - Orientation = Orientation.Horizontal, - Children = - { - new WImage { Source = new BitmapImage(new Uri("/" + Element.Image.File, UriKind.Relative)), Width = 30, Height = 30, Margin = new WThickness(0, 0, 20, 0) }, - new TextBlock { Text = Element.Text } - } - }; + case Button.ButtonContentLayout.ImagePosition.Top: + container.Orientation = Orientation.Vertical; + image.Margin = new WThickness(0, 0, 0, spacing); + break; + case Button.ButtonContentLayout.ImagePosition.Bottom: + container.Orientation = Orientation.Vertical; + image.Margin = new WThickness(0, spacing, 0, 0); + break; + case Button.ButtonContentLayout.ImagePosition.Right: + container.Orientation = Orientation.Horizontal; + image.Margin = new WThickness(spacing, 0, 0, 0); + break; + default: + // Defaults to image on the left + container.Orientation = Orientation.Horizontal; + image.Margin = new WThickness(0, 0, spacing, 0); + break; } - else - Control.Content = Element.Text; + + container.Children.Add(image); + container.Children.Add(textBlock); + + Control.Content = container; } void UpdateFont() diff --git a/Xamarin.Forms.Platform.WinRT/ButtonRenderer.cs b/Xamarin.Forms.Platform.WinRT/ButtonRenderer.cs index 60ee6418..bcba31cc 100644 --- a/Xamarin.Forms.Platform.WinRT/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.WinRT/ButtonRenderer.cs @@ -117,25 +117,78 @@ namespace Xamarin.Forms.Platform.WinRT void UpdateContent() { - if (Element.Image != null) + var text = Element.Text; + var elementImage = Element.Image; + + // No image, just the text + if (elementImage == null) { - var panel = new StackPanel { Orientation = Orientation.Horizontal }; + Control.Content = text; + return; + } - var image = new WImage { Source = new BitmapImage(new Uri("ms-appx:///" + Element.Image.File)), Width = 30, Height = 30, Margin = new WThickness(0, 0, 20, 0) }; - panel.Children.Add(image); - image.ImageOpened += (sender, args) => { ((IButtonController)Element).NativeSizeChanged(); }; + var image = new WImage + { + Source = new BitmapImage(new Uri("ms-appx:///" + elementImage.File)), + Width = 30, + Height = 30, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; + + // No text, just the image + if (string.IsNullOrEmpty(text)) + { + Control.Content = image; + return; + } - if (Element.Text != null) - { - panel.Children.Add(new TextBlock { Text = Element.Text }); - } + // Both image and text, so we need to build a container for them + var layout = Element.ContentLayout; + var container = new StackPanel(); + var textBlock = new TextBlock + { + Text = text, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; - Control.Content = panel; - } - else + var spacing = layout.Spacing; + + container.HorizontalAlignment = HorizontalAlignment.Center; + container.VerticalAlignment = VerticalAlignment.Center; + + switch (layout.Position) { - Control.Content = Element.Text; + case Button.ButtonContentLayout.ImagePosition.Top: + container.Orientation = Orientation.Vertical; + image.Margin = new WThickness(0, 0, 0, spacing); + container.Children.Add(image); + container.Children.Add(textBlock); + break; + case Button.ButtonContentLayout.ImagePosition.Bottom: + container.Orientation = Orientation.Vertical; + image.Margin = new WThickness(0, spacing, 0, 0); + container.Children.Add(textBlock); + container.Children.Add(image); + break; + case Button.ButtonContentLayout.ImagePosition.Right: + container.Orientation = Orientation.Horizontal; + image.Margin = new WThickness(spacing, 0, 0, 0); + container.Children.Add(textBlock); + container.Children.Add(image); + break; + default: + // Defaults to image on the left + container.Orientation = Orientation.Horizontal; + image.Margin = new WThickness(0, 0, spacing, 0); + container.Children.Add(image); + container.Children.Add(textBlock); + break; } + + Control.Content = container; + } void UpdateFont() diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs index 3fa84d1d..34609b49 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs @@ -1,20 +1,18 @@ using System; -using System.Drawing; using System.Linq; using System.ComponentModel; +using System.Diagnostics; + #if __UNIFIED__ +using Foundation; using UIKit; -using CoreGraphics; -#else -using MonoTouch.UIKit; -using MonoTouch.CoreGraphics; -#endif -#if __UNIFIED__ using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; using PointF = CoreGraphics.CGPoint; - #else +using System.Drawing; +using MonoTouch.UIKit; +using MonoTouch.Foundation; using nfloat=System.Single; using nint=System.Int32; using nuint=System.UInt32; @@ -27,14 +25,25 @@ namespace Xamarin.Forms.Platform.iOS UIColor _buttonTextColorDefaultDisabled; UIColor _buttonTextColorDefaultHighlighted; UIColor _buttonTextColorDefaultNormal; + bool _titleChanged; + SizeF _titleSize; + + // This looks like it should be a const under iOS Classic, + // but that doesn't work under iOS + // ReSharper disable once BuiltInTypeReferenceStyle + // Under iOS Classic Resharper wants to suggest this use the built-in type ref + // but under iOS that suggestion won't work + readonly nfloat _minimumButtonHeight = 44; // Apple docs public override SizeF SizeThatFits(SizeF size) { var result = base.SizeThatFits(size); - result.Height = 44; // Apple docs - //Compensate for the insets - if (!Control.ImageView.Hidden) - result.Width += 10; + + if (result.Height < _minimumButtonHeight) + { + result.Height = _minimumButtonHeight; + } + return result; } @@ -56,6 +65,8 @@ namespace Xamarin.Forms.Platform.iOS { SetNativeControl(new UIButton(UIButtonType.RoundedRect)); + Debug.Assert(Control != null, "Control != null"); + _buttonTextColorDefaultNormal = Control.TitleColor(UIControlState.Normal); _buttonTextColorDefaultHighlighted = Control.TitleColor(UIControlState.Highlighted); _buttonTextColorDefaultDisabled = Control.TitleColor(UIControlState.Disabled); @@ -91,8 +102,7 @@ namespace Xamarin.Forms.Platform.iOS void OnButtonTouchUpInside(object sender, EventArgs eventArgs) { - if (Element != null) - ((IButtonController)Element).SendClicked(); + ((IButtonController)Element)?.SendClicked(); } void UpdateBackgroundVisibility() @@ -129,7 +139,7 @@ namespace Xamarin.Forms.Platform.iOS async void UpdateImage() { IImageSourceHandler handler; - var source = Element.Image; + FileImageSource source = Element.Image; if (source != null && (handler = Registrar.Registered.GetHandler(source.GetType())) != null) { UIImage uiimage; @@ -141,31 +151,36 @@ namespace Xamarin.Forms.Platform.iOS { uiimage = null; } - var button = Control; + UIButton button = Control; if (button != null && uiimage != null) { if (Forms.IsiOS7OrNewer) button.SetImage(uiimage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal); else button.SetImage(uiimage, UIControlState.Normal); + button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit; - Control.ImageEdgeInsets = new UIEdgeInsets(0, 0, 0, 10); - Control.TitleEdgeInsets = new UIEdgeInsets(0, 10, 0, 0); + ComputeEdgeInsets(Control, Element.ContentLayout); } } else { Control.SetImage(null, UIControlState.Normal); - Control.ImageEdgeInsets = new UIEdgeInsets(0, 0, 0, 0); - Control.TitleEdgeInsets = new UIEdgeInsets(0, 0, 0, 0); + ClearEdgeInsets(Control); } ((IVisualElementController)Element).NativeSizeChanged(); } void UpdateText() { - Control.SetTitle(Element.Text, UIControlState.Normal); + var newText = Element.Text; + + if (Control.Title(UIControlState.Normal) != newText) + { + Control.SetTitle(Element.Text, UIControlState.Normal); + _titleChanged = true; + } } void UpdateTextColor() @@ -186,5 +201,74 @@ namespace Xamarin.Forms.Platform.iOS Control.TintColor = Element.TextColor.ToUIColor(); } } + + void ClearEdgeInsets(UIButton button) + { + if (button == null) + { + return; + } + + Control.ImageEdgeInsets = new UIEdgeInsets(0, 0, 0, 0); + Control.TitleEdgeInsets = new UIEdgeInsets(0, 0, 0, 0); + Control.ContentEdgeInsets = new UIEdgeInsets(0, 0, 0, 0); + } + + void ComputeEdgeInsets(UIButton button, Button.ButtonContentLayout layout) + { + if (button?.ImageView?.Image == null || string.IsNullOrEmpty(button.TitleLabel?.Text)) + { + return; + } + + var position = layout.Position; + var spacing = (nfloat)(layout.Spacing / 2); + + if (position == Button.ButtonContentLayout.ImagePosition.Left) + { + button.ImageEdgeInsets = new UIEdgeInsets(0, -spacing, 0, spacing); + button.TitleEdgeInsets = new UIEdgeInsets(0, spacing, 0, -spacing); + button.ContentEdgeInsets = new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing); + return; + } + + if (_titleChanged) + { + var stringToMeasure = new NSString(button.TitleLabel.Text); + UIStringAttributes attribs = new UIStringAttributes { Font = button.TitleLabel.Font }; + _titleSize = stringToMeasure.GetSizeUsingAttributes(attribs); + _titleChanged = false; + } + + var labelWidth = _titleSize.Width; + var imageWidth = button.ImageView.Image.Size.Width; + + if (position == Button.ButtonContentLayout.ImagePosition.Right) + { + button.ImageEdgeInsets = new UIEdgeInsets(0, labelWidth + spacing, 0, -labelWidth - spacing); + button.TitleEdgeInsets = new UIEdgeInsets(0, -imageWidth - spacing, 0, imageWidth + spacing); + button.ContentEdgeInsets = new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing); + return; + } + + var imageVertOffset = (_titleSize.Height / 2); + var titleVertOffset = (button.ImageView.Image.Size.Height / 2); + + var edgeOffset = (float)Math.Min(imageVertOffset, titleVertOffset); + + button.ContentEdgeInsets = new UIEdgeInsets(edgeOffset, 0, edgeOffset, 0); + + var horizontalImageOffset = labelWidth / 2; + var horizontalTitleOffset = imageWidth / 2; + + if (position == Button.ButtonContentLayout.ImagePosition.Bottom) + { + imageVertOffset = -imageVertOffset; + titleVertOffset = -titleVertOffset; + } + + button.ImageEdgeInsets = new UIEdgeInsets(-imageVertOffset, horizontalImageOffset, imageVertOffset, -horizontalImageOffset); + button.TitleEdgeInsets = new UIEdgeInsets(titleVertOffset, -horizontalTitleOffset, -titleVertOffset, horizontalTitleOffset); + } } } \ No newline at end of file diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout+ImagePosition.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout+ImagePosition.xml new file mode 100644 index 00000000..fd06aed0 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout+ImagePosition.xml @@ -0,0 +1,73 @@ + + + + + Xamarin.Forms.Core + 2.0.0.0 + + + System.Enum + + + To be added. + To be added. + + + + + + Field + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout+ImagePosition + + + To be added. + + + + + + Field + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout+ImagePosition + + + To be added. + + + + + + Field + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout+ImagePosition + + + To be added. + + + + + + Field + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout+ImagePosition + + + To be added. + + + + diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout.xml new file mode 100644 index 00000000..63f20391 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentLayout.xml @@ -0,0 +1,93 @@ + + + + + Xamarin.Forms.Core + 2.0.0.0 + + + System.Object + + + + + System.Diagnostics.DebuggerDisplay("Image Position = {Position}, Spacing = {Spacing}") + + + Xamarin.Forms.TypeConverter(typeof(Xamarin.Forms.Button/ButtonContentTypeConverter)) + + + + To be added. + To be added. + + + + + + Constructor + + 2.0.0.0 + + + + + + + To be added. + To be added. + To be added. + To be added. + + + + + + Property + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout+ImagePosition + + + To be added. + To be added. + To be added. + + + + + + Property + + 2.0.0.0 + + + System.Double + + + To be added. + To be added. + To be added. + + + + + + Method + + 2.0.0.0 + + + System.String + + + + To be added. + To be added. + To be added. + + + + diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentTypeConverter.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentTypeConverter.xml new file mode 100644 index 00000000..aba79300 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button+ButtonContentTypeConverter.xml @@ -0,0 +1,51 @@ + + + + + Xamarin.Forms.Core + 2.0.0.0 + + + Xamarin.Forms.TypeConverter + + + + To be added. + To be added. + + + + + + Constructor + + 2.0.0.0 + + + + To be added. + To be added. + + + + + + Method + + 2.0.0.0 + + + System.Object + + + + + + To be added. + To be added. + To be added. + To be added. + + + + diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/Button.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button.xml index b5614e13..09f52769 100644 --- a/docs/Xamarin.Forms.Core/Xamarin.Forms/Button.xml +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/Button.xml @@ -358,6 +358,37 @@ namespace FormsGallery + + + + Property + + 2.0.0.0 + + + Xamarin.Forms.Button+ButtonContentLayout + + + To be added. + To be added. + To be added. + + + + + + Field + + 2.0.0.0 + + + Xamarin.Forms.BindableProperty + + + To be added. + To be added. + + -- cgit v1.2.3