ImageButton (#1974)
authorShane Neuville <shane94@hotmail.com>
Sat, 3 Nov 2018 22:45:28 +0000 (16:45 -0600)
committerGitHub <noreply@github.com>
Sat, 3 Nov 2018 22:45:28 +0000 (16:45 -0600)
* ImageButton Implementation

* [Android] rename BorderBackgroundManager

* [Android] copy AspectFill changes to ImageButton

* [UITests] fix order of gallery pages to work with tests

* [Android] Account for changing image region with border/shadows

* [Android] check for api 18 and add setpadding call back in

* [Android] throw exception instead of crash when image fails to load

* Addressing issue comments and adding additional test cases to Issue demo

- fixes #1724

76 files changed:
Stubs/Xamarin.Forms.Platform.cs
Xamarin.Forms.ControlGallery.WindowsUniversal/Fruits.jpg [new file with mode: 0644]
Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
Xamarin.Forms.ControlGallery.WindowsUniversal/calculator.png [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue1724.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Controls/CoreGallery.cs
Xamarin.Forms.Controls/CoreGalleryPages/ButtonCoreGalleryPage.cs
Xamarin.Forms.Controls/CoreGalleryPages/ImageButtonCoreGalleryPage.cs [new file with mode: 0644]
Xamarin.Forms.Core.UITests.Shared/PlatformQueries.cs
Xamarin.Forms.Core.UITests.Shared/Queries.cs
Xamarin.Forms.Core.UITests.Shared/Remotes/BaseViewContainerRemote.cs
Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs [new file with mode: 0644]
Xamarin.Forms.Core.UITests.Shared/Xamarin.Forms.Core.UITests.projitems
Xamarin.Forms.Core.UnitTests/ImageButtonUnitTest.cs [new file with mode: 0644]
Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
Xamarin.Forms.Core/Button.cs
Xamarin.Forms.Core/ButtonElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Core/IBorderController.cs [new file with mode: 0644]
Xamarin.Forms.Core/IButtonController.cs
Xamarin.Forms.Core/IImageController.cs
Xamarin.Forms.Core/Image.cs
Xamarin.Forms.Core/ImageButton.cs [new file with mode: 0644]
Xamarin.Forms.Core/ImageElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Core/PlatformConfiguration/AndroidSpecific/ImageButton.cs [new file with mode: 0644]
Xamarin.Forms.Core/VisualElement.cs
Xamarin.Forms.CustomAttributes/TestAttributes.cs
Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
Xamarin.Forms.Platform.Android/AppCompat/ImageButtonRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/BackgroundManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/BorderBackgroundManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/ButtonBackgroundTracker.cs [deleted file]
Xamarin.Forms.Platform.Android/Cells/BaseCellView.cs
Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs
Xamarin.Forms.Platform.Android/FastRenderers/ButtonElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs
Xamarin.Forms.Platform.Android/FastRenderers/ImageElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs
Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs
Xamarin.Forms.Platform.Android/IBorderVisualElementRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/IVisualElementRenderer.cs
Xamarin.Forms.Platform.Android/Properties/AssemblyInfo.cs
Xamarin.Forms.Platform.Android/Renderers/BorderDrawable.cs [moved from Xamarin.Forms.Platform.Android/Renderers/ButtonDrawable.cs with 75% similarity]
Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/FormsImageView.cs
Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/NavigationMenuRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/SwitchRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/ToolbarImageButton.cs
Xamarin.Forms.Platform.Android/VisualElementRenderer.cs
Xamarin.Forms.Platform.Android/VisualElementTracker.cs
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
Xamarin.Forms.Platform.GTK/Renderers/ButtonRenderer.cs
Xamarin.Forms.Platform.GTK/Renderers/ImageRenderer.cs
Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
Xamarin.Forms.Platform.UAP/IImageVisualElementRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/IVisualNativeElementRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/ImageButtonRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/ImageElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/ImageRenderer.cs
Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs
Xamarin.Forms.Platform.UAP/VisualElementRenderer.cs
Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
Xamarin.Forms.Platform.iOS/IVisualNativeElementRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/BorderElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ButtonElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ButtonRenderer.cs
Xamarin.Forms.Platform.iOS/Renderers/IImageVisualElementRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ImageButtonRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
Xamarin.Forms.Platform.iOS/ViewRenderer.cs
Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs
Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
Xamarin.Forms.Platform/Xamarin.Forms.Platform.cs

index 5d7aa83..d5b07c6 100644 (file)
@@ -51,6 +51,13 @@ namespace Xamarin.Forms.Platform
        [RenderWith (typeof (ButtonRenderer))]
        internal class _ButtonRenderer { }
 
+#if __ANDROID__
+       [RenderWith(typeof(ImageButtonRenderer))]
+#elif !TIZEN4_0
+       [RenderWith(typeof(ImageButtonRenderer))]
+#endif
+       internal class _ImageButtonRenderer { }
+
        [RenderWith (typeof (TableViewRenderer))]
        internal class _TableViewRenderer { }
 
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Fruits.jpg b/Xamarin.Forms.ControlGallery.WindowsUniversal/Fruits.jpg
new file mode 100644 (file)
index 0000000..6c4ea3d
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WindowsUniversal/Fruits.jpg differ
index 3da2d10..a3acced 100644 (file)
     <Compile Include="_60122ImageRenderer.cs" />
     <Content Include="Assets\Fonts\OFL.txt" />
     <Content Include="bank.png" />
+    <Content Include="calculator.png" />
     <Content Include="coffee.png" />
     <Content Include="cover1.jpg" />
     <Content Include="cover1small.jpg" />
     <Content Include="crimson.jpg" />
     <Content Include="crimsonsmall.jpg" />
     <Content Include="default.css" />
+    <Content Include="Fruits.jpg" />
     <Content Include="invalidimage.jpg" />
     <Content Include="local.html" />
     <Content Include="test.jpg" />
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/calculator.png b/Xamarin.Forms.ControlGallery.WindowsUniversal/calculator.png
new file mode 100644 (file)
index 0000000..339cab5
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WindowsUniversal/calculator.png differ
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue1724.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue1724.cs
new file mode 100644 (file)
index 0000000..3408cc4
--- /dev/null
@@ -0,0 +1,250 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using Xamarin.Forms.PlatformConfiguration;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Github, 1724, "[Enhancement] ImageButton", PlatformAffected.All)]
+       public class Issue1724 : TestContentPage
+       {
+               bool animation = false;
+
+               protected override void Init()
+               {
+                       int sizes = 200;
+                       var radius = new ImageButton()
+                       {
+                               BorderColor = Color.Brown,
+                               BorderWidth = 5,
+                               BackgroundColor = Color.Yellow,
+                               Aspect = Aspect.Fill,
+                               CornerRadius = 10,
+                               Source = "coffee.png",
+                               HorizontalOptions = LayoutOptions.Center,
+                               HeightRequest = sizes,
+                               WidthRequest = sizes,
+
+                       };
+
+                       radius.On<Android>().SetShadowColor(Color.Green);
+                       radius.On<Android>().SetIsShadowEnabled(true);
+                       radius.On<Android>().SetShadowOffset(new Size(25, 25));
+
+                       var radiusBackground = new ImageButton()
+                       {
+                               BorderColor = Color.Brown,
+                               BorderWidth = 5,
+                               Source = "coffee.png",
+                               CornerRadius = 10,
+                               HorizontalOptions = LayoutOptions.Center,
+                               BackgroundColor = Color.Pink,
+                               HeightRequest = sizes,
+                               WidthRequest = sizes
+                       };
+
+                       radiusBackground.On<Android>().SetShadowColor(Color.Green);
+                       radiusBackground.On<Android>().SetIsShadowEnabled(true);
+                       radiusBackground.On<Android>().SetShadowOffset(new Size(5, 5));
+
+
+                       StackLayout layout = new StackLayout()
+                       {
+                               Children =
+                               {
+                                       new Label(){ Text = "No padding?" },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               BackgroundColor = Color.GreenYellow
+                                       },
+                                       new Label(){ Text = "Do I have left padding? I should have left padding." },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               BackgroundColor = Color.Green,
+                                               Padding = new Thickness(100, 0, 0, 0)
+                                       },
+                                       new Label(){ Text = "Do I have top padding? I should have top padding." },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               BackgroundColor = Color.LawnGreen,
+                                               Padding = new Thickness(0, 30, 0, 0)
+                                       },
+                                       new Label(){ Text = "Do I have right padding? I should have right padding."},
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               BackgroundColor = Color.LightGreen,
+                                               Padding = new Thickness(0, 0, 100, 0)
+                                       },
+                                       new Label(){ Text = "Do I have bottom padding? I should have bottom padding." },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               BackgroundColor = Color.ForestGreen,
+                                               Padding = new Thickness(0, 0, 0, 30)
+                                       },
+                                       new Label(){ Text = "Do you see image from a Uri?" },
+                                       new ImageButton()
+                                       {
+                                               Source = "https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.Controls/coffee.png",
+                                               BackgroundColor = Color.ForestGreen
+                                       },
+                                       new Label(){ Text = "Invalid Image Uri just to test it doesn't crash" },
+                                       new ImageButton()
+                                       {
+                                               Source = "http://xamarin.com/imginvalidf@#$R(P&fb.png",
+                                               BackgroundColor = Color.ForestGreen
+                                       },
+                                       new Label(){ Text = "Aspect: Aspect.Fill with shadows" },
+                                       radius,
+                                       new Label(){ Text = "Aspect: Aspect.AspectFit with shadows" },
+                                       radiusBackground,
+                                       new Label(){ Text = "BorderColor:Color.Green, BorderWidth:10" },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               HorizontalOptions = LayoutOptions.Center,
+                                               HeightRequest = sizes,
+                                               WidthRequest = sizes,
+                                               BorderColor = Color.Green,
+                                               BorderWidth = 10
+                                       },
+                                       new Label(){ Text = "BorderColor:Color.Green, BorderWidth:10, Aspect:Aspect.Fill" },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               HorizontalOptions = LayoutOptions.Center,
+                                               HeightRequest = sizes,
+                                               WidthRequest = sizes,
+                                               BorderColor = Color.Green,
+                                               BorderWidth = 10,
+                                               Aspect = Aspect.Fill
+                                       },
+                                       new Label(){ Text = "BackgroundColor:Green" },
+                                       new ImageButton()
+                                       {
+                                               Source = "coffee.png",
+                                               HorizontalOptions = LayoutOptions.Center,
+                                               HeightRequest = sizes,
+                                               WidthRequest = sizes,
+                                               BackgroundColor = Color.Green
+                                       },
+                                       new Label(){ Text = "BorderWidth: 5, CornerRadius:10, BorderColor:Brown" },
+                                       new ImageButton()
+                                       {
+                                               BorderColor = Color.Brown,
+                                               BorderWidth = 5,
+                                               Source = "coffee.png",
+                                               CornerRadius = 10,
+                                               HorizontalOptions = LayoutOptions.Center,
+                                               HeightRequest = sizes,
+                                               WidthRequest = sizes
+                                       }
+                               }
+                       };
+
+                       var buttons = layout.Children.OfType<ImageButton>();
+                       layout.Children.Insert(0, ActionGrid(buttons.ToList()));
+                       PaddingAnimation(buttons).Start();
+
+                       Content = new ScrollView() { Content = layout };
+               }
+
+               Grid ActionGrid(List<ImageButton> buttons)
+               {
+                       ImageButton firstButton = buttons.FirstOrDefault();
+                       Grid actionGrid = new Grid();
+                       actionGrid.AddChild(new Button()
+                       {
+                               Text = "Add Right",
+                               Command = new Command(() =>
+                               {
+                                       var button = firstButton;
+                                       button.Padding = new Thickness(button.Padding.Left, 0, button.Padding.Right + 10, 0);
+                               })
+                       }, 0, 0);
+                       actionGrid.AddChild(new Button()
+                       {
+                               Text = "Add Left",
+                               Command = new Command(() =>
+                               {
+                                       var button = firstButton;
+                                       button.Padding = new Thickness(button.Padding.Left + 10, 0, button.Padding.Right, 0);
+                               })
+                       }, 0, 1);
+
+                       actionGrid.AddChild(new Button()
+                       {
+                               Text = "Animation",
+                               Command = new Command(() => animation = !animation)
+                       }, 1, 1);
+                       actionGrid.AddChild(new Button()
+                       {
+                               Text = "Add Top",
+                               Command = new Command(() =>
+                               {
+                                       var button = firstButton;
+                                       button.Padding = new Thickness(0, button.Padding.Top + 10, 0, button.Padding.Bottom);
+                               })
+                       }, 2, 0);
+                       actionGrid.AddChild(new Button()
+                       {
+                               Text = "Add Bottom",
+                               Command = new Command(() =>
+                               {
+                                       var button = firstButton;
+                                       button.Padding = new Thickness(0, button.Padding.Top, 0, button.Padding.Bottom + 10);
+                               })
+                       }, 2, 1);
+                       return actionGrid;
+               }
+
+               Thread PaddingAnimation(IEnumerable<ImageButton> buttons)
+               {
+                       return new Thread(() =>
+                       {
+                               int increment = 1;
+                               int current = 0;
+                               int max = 15;
+                               int FPS = 30;
+                               int sleep = 1000 / FPS;
+
+                               while (true)
+                               {
+                                       Thread.Sleep(sleep);
+
+                                       if (!animation)
+                                               continue;
+
+                                       current += increment;
+                                       if (current > max || current < 0)
+                                       {
+                                               increment *= -1;
+                                               current += increment * 2;
+                                       }
+
+                                       Device.BeginInvokeOnMainThread(() =>
+                                       {
+                                               foreach (var button in buttons)
+                                               {
+                                                       var padding = button.Padding;
+                                                       button.Padding = padding = new Thickness(
+                                                               padding.Left + increment,
+                                                               padding.Top + increment,
+                                                               padding.Right + increment,
+                                                               padding.Bottom + increment);
+                                               }
+                                       });
+                               }
+                       });
+               }
+       }
+}
\ No newline at end of file
index 103c79c..344a9d6 100644 (file)
@@ -24,6 +24,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)Issue3306.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue3308.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue3788.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Issue1724.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue3524.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue2004.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue3333.cs" />
index df04528..c015a1c 100644 (file)
@@ -303,6 +303,7 @@ namespace Xamarin.Forms.Controls
                                new GalleryPageFactory(() => new EditorCoreGalleryPage(), "Editor Gallery"),
                                new GalleryPageFactory(() => new FrameCoreGalleryPage(), "Frame Gallery"),
                                new GalleryPageFactory(() => new ImageCoreGalleryPage(), "Image Gallery"),
+                               new GalleryPageFactory(() => new ImageButtonCoreGalleryPage(), "Image Button Gallery"),
                                new GalleryPageFactory(() => new KeyboardCoreGallery(), "Keyboard Gallery"),
                                new GalleryPageFactory(() => new LabelCoreGalleryPage(), "Label Gallery"),
                                new GalleryPageFactory(() => new ListViewCoreGalleryPage(), "ListView Gallery"),
index 1e335cd..3a285a3 100644 (file)
@@ -60,6 +60,14 @@ namespace Xamarin.Forms.Controls
                        );
                        clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired ();
 
+                       var pressedContainer = new EventViewContainer<Button>(Test.Button.Pressed,
+                               new Button
+                               {
+                                       Text = "Pressed"
+                               }
+                       );
+                       pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
+
                        var commandContainer = new ViewContainer<Button> (Test.Button.Command, 
                                new Button {
                                        Text = "Command", 
@@ -103,6 +111,7 @@ namespace Xamarin.Forms.Controls
                        Add (borderRadiusContainer);
                        Add (borderWidthContainer);
                        Add (clickedContainer);
+                       Add(pressedContainer);
                        Add (commandContainer);
                        Add (fontContainer);
                        Add (imageContainer);
diff --git a/Xamarin.Forms.Controls/CoreGalleryPages/ImageButtonCoreGalleryPage.cs b/Xamarin.Forms.Controls/CoreGalleryPages/ImageButtonCoreGalleryPage.cs
new file mode 100644 (file)
index 0000000..0b51bf5
--- /dev/null
@@ -0,0 +1,122 @@
+using System;
+
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.Controls
+{
+       internal class ImageButtonCoreGalleryPage : CoreGalleryPage<ImageButton>
+       {
+               protected override bool SupportsFocus => false;
+
+               protected override bool SupportsTapGestureRecognizer => false;
+
+               protected override void InitializeElement(ImageButton element)
+               {
+                       element.Source = "oasissmall.jpg";
+               }
+
+               protected override void Build(StackLayout stackLayout)
+               {
+                       base.Build(stackLayout);
+
+
+                       IsEnabledStateViewContainer.View.Clicked += (sender, args) =>
+                       {
+                               IsEnabledStateViewContainer.TitleLabel.Text += " (Tapped)";
+                       };
+
+                       var aspectFillContainer = new ViewContainer<ImageButton>(Test.ImageButton.AspectFill, new ImageButton { Aspect = Aspect.AspectFill });
+                       var aspectFitContainer = new ViewContainer<ImageButton>(Test.ImageButton.AspectFit, new ImageButton { Aspect = Aspect.AspectFit });
+                       var fillContainer = new ViewContainer<ImageButton>(Test.ImageButton.Fill, new ImageButton { Aspect = Aspect.Fill });
+                       var isLoadingContainer = new StateViewContainer<ImageButton>(Test.ImageButton.IsLoading, new ImageButton());
+                       var isOpaqueContainer = new StateViewContainer<ImageButton>(Test.ImageButton.IsOpaque, new ImageButton());
+
+
+                       var borderButtonContainer = new ViewContainer<ImageButton>(Test.ImageButton.BorderColor,
+                               new ImageButton
+                               {
+                                       BackgroundColor = Color.Transparent,
+                                       BorderColor = Color.Red,
+                                       BorderWidth = 1,
+                                       Source = "oasissmall.jpg"
+                               }
+                       );
+
+                       var corderRadiusContainer = new ViewContainer<ImageButton>(Test.ImageButton.CornerRadius,
+                               new ImageButton
+                               {
+                                       Source = "oasissmall.jpg",
+                                       BackgroundColor = Color.Transparent,
+                                       BorderColor = Color.Red,
+                                       CornerRadius = 20,
+                                       BorderWidth = 1,
+                               }
+                       );
+
+                       var borderWidthContainer = new ViewContainer<ImageButton>(Test.ImageButton.BorderWidth,
+                               new ImageButton
+                               {
+                                       Source = "oasissmall.jpg",
+                                       BackgroundColor = Color.Transparent,
+                                       BorderColor = Color.Red,
+                                       BorderWidth = 15,
+                               }
+                       );
+
+                       var clickedContainer = new EventViewContainer<ImageButton>(Test.ImageButton.Clicked,
+                               new ImageButton
+                               {
+                                       Source = "oasissmall.jpg"
+                               }
+                       );
+                       clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired();
+
+                       var pressedContainer = new EventViewContainer<ImageButton>(Test.ImageButton.Pressed,
+                               new ImageButton
+                               {
+                                       Source = "oasissmall.jpg"
+                               }
+                       );
+                       pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
+
+                       var commandContainer = new ViewContainer<ImageButton>(Test.ImageButton.Command,
+                               new ImageButton
+                               {
+                                       Command = new Command(() => DisplayActionSheet("Hello Command", "Cancel", "Destroy")),
+                                       Source = "oasissmall.jpg"
+                               }
+                       );
+
+                       var imageContainer = new ViewContainer<ImageButton>(Test.ImageButton.Image,
+                               new ImageButton
+                               {
+                                       Source = new FileImageSource { File = "bank.png" }
+                               }
+                       );
+
+
+                       InitializeElement(aspectFillContainer.View);
+                       InitializeElement(aspectFitContainer.View);
+                       InitializeElement(fillContainer.View);
+                       InitializeElement(isLoadingContainer.View);
+                       InitializeElement(isOpaqueContainer.View);
+
+                       var sourceContainer = new ViewContainer<ImageButton>(Test.ImageButton.Source, new ImageButton { Source = "https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.Controls/coffee.png" });
+
+                       Add(aspectFillContainer);
+                       Add(aspectFitContainer);
+                       Add(fillContainer);
+                       Add(isLoadingContainer);
+                       Add(isOpaqueContainer);
+                       Add(sourceContainer);
+
+                       Add(borderButtonContainer);
+                       Add(borderWidthContainer);
+                       Add(clickedContainer);
+                       Add(commandContainer);
+                       Add(corderRadiusContainer);
+                       Add(imageContainer);
+                       Add(pressedContainer);
+               }
+       }
+}
\ No newline at end of file
index daa122e..10eafb4 100644 (file)
@@ -15,6 +15,8 @@ namespace Xamarin.Forms.Core.UITests
                        { Button.FontProperty, Tuple.Create (new[] { "titleLabel", "font" }, false) },
                        { Button.TextProperty, Tuple.Create (new[] { "titleLabel", "text" }, false) },
                        { Button.TextColorProperty, Tuple.Create (new[] { "titleLabel", "textColor" }, false) },
+                       { ImageButton.CornerRadiusProperty, Tuple.Create (new[] { "layer", "cornerRadius" }, false) },
+                       { ImageButton.BorderWidthProperty, Tuple.Create (new[] { "layer", "borderWidth" }, false) },
                        { View.AnchorXProperty, Tuple.Create (new[] { "layer", "transform" }, true) },
                        { View.AnchorYProperty, Tuple.Create (new[] { "layer", "transform" }, true) },
                        { View.BackgroundColorProperty, Tuple.Create (new[] { "backgroundColor" }, false) },
@@ -32,13 +34,16 @@ namespace Xamarin.Forms.Core.UITests
                        {
                                { ActivityIndicator.ColorProperty, Tuple.Create(new[] { "getProgressDrawable", "getColor" }, false) },
                                { ActivityIndicator.IsRunningProperty, Tuple.Create(new[] { "isIndeterminate" }, false) },
-                               { Button.BorderColorProperty, Tuple.Create(new[] { "getBackground" }, false) },
+                               { BorderElement.BorderColorProperty, Tuple.Create(new[] { "getBackground" }, false) },
                                { Button.CornerRadiusProperty, Tuple.Create(new[] { "getBackground" }, false) },
                                { Button.BorderWidthProperty, Tuple.Create(new[] { "getBackground" }, false) },
                                { Button.ImageProperty, Tuple.Create(new[] { "getBackground" }, false) },
                                { Button.FontProperty, Tuple.Create(new[] { "getTypeface", "isBold" }, false) },
                                { Button.TextProperty, Tuple.Create(new[] { "getText" }, false) },
                                { Button.TextColorProperty, Tuple.Create(new[] { "getCurrentTextColor" }, false) },
+                               { ImageButton.CornerRadiusProperty, Tuple.Create(new[] { "getBackground" }, false) },
+                               { ImageButton.BorderWidthProperty, Tuple.Create(new[] { "getBackground" }, false) },
+                               { ImageButton.SourceProperty, Tuple.Create(new[] { "getBackground" }, false) },
                                { View.AnchorXProperty, Tuple.Create(new[] { "getPivotX" }, true) },
                                { View.AnchorYProperty, Tuple.Create(new[] { "getPivotY" }, true) },
                                { View.BackgroundColorProperty, Tuple.Create(new[] { "getBackground", "getColor" }, true) },
@@ -63,6 +68,7 @@ namespace Xamarin.Forms.Core.UITests
                public static readonly string Entry = "UITextField";
                public static readonly string Frame = "view:'Xamarin_Forms_Platform_iOS_FrameRenderer'";
                public static readonly string Image = "UIImageView";
+               public static readonly string ImageButton = "UIButton";
                public static readonly string Label = "UILabel";
                public static readonly string ListView = "UITableView";
                public static readonly string Map = "MKMapView";
@@ -86,6 +92,7 @@ namespace Xamarin.Forms.Core.UITests
                public static readonly string Entry = "xamarin.forms.platform.android.EntryEditText";
                public static readonly string Frame = "xamarin.forms.platform.android.appcompat.FrameRenderer";
                public static readonly string Image = "android.widget.ImageView";
+               public static readonly string ImageButton = "android.widget.ImageButton";
                public static readonly string Label = "android.widget.TextView";
                public static readonly string ListView = "android.widget.ListView";
                public static readonly string Map = "android.gms.maps.GoogleMap";
index 69fe33d..d20596c 100644 (file)
@@ -10,7 +10,7 @@ using Xamarin.UITest;
 using Xamarin.UITest.Queries;
 
 namespace Xamarin.Forms.Core.UITests
-{      
+{
        internal static class GalleryQueries
        {
                public const string AutomationIDGallery = "* marked:'AutomationID Gallery'";
@@ -24,6 +24,7 @@ namespace Xamarin.Forms.Core.UITests
                public const string EntryGallery = "* marked:'Entry Gallery'";
                public const string FrameGallery = "* marked:'Frame Gallery'";
                public const string ImageGallery = "* marked:'Image Gallery'";
+               public const string ImageButtonGallery = "* marked:'Image Button Gallery'";
                public const string LabelGallery = "* marked:'Label Gallery'";
                public const string ListViewGallery = "* marked:'ListView Gallery'";
                public const string OpenGLViewGallery = "* marked:'OpenGLView Gallery'";
@@ -55,17 +56,17 @@ namespace Xamarin.Forms.Core.UITests
        {
                #region Platform queries
 
-               public static Func<AppQuery, AppQuery> NavigationBarBackButton ()
+               public static Func<AppQuery, AppQuery> NavigationBarBackButton()
                {
                        return PlatformQueries.NavigationBarBackButton;
                }
 
-               public static Func<AppQuery, AppQuery> PageWithoutNavigationBar ()
+               public static Func<AppQuery, AppQuery> PageWithoutNavigationBar()
                {
                        return PlatformQueries.PageWithoutNavigationBar;
                }
 
-               public static Func<AppQuery, AppQuery> Root ()
+               public static Func<AppQuery, AppQuery> Root()
                {
                        return PlatformQueries.Root;
                }
@@ -84,6 +85,7 @@ namespace Xamarin.Forms.Core.UITests
                public static readonly string Entry = PlatformViews.Entry;
                public static readonly string Frame = PlatformViews.Frame;
                public static readonly string Image = PlatformViews.Image;
+               public static readonly string ImageButton = PlatformViews.ImageButton;
                public static readonly string Label = PlatformViews.Label;
                public static readonly string ListView = PlatformViews.ListView;
                public static readonly string Map = PlatformViews.Map;
@@ -102,12 +104,12 @@ namespace Xamarin.Forms.Core.UITests
 
        internal static class Rects
        {
-               public static AppRect RootViewRect (this IApp app)
+               public static AppRect RootViewRect(this IApp app)
                {
 #if __WINDOWS__
                        return app.Query(WinDriverApp.AppName)[0].Rect;
 #else
-                       return app.Query (q => q.Raw ("* index:0"))[0].Rect;
+                       return app.Query(q => q.Raw("* index:0"))[0].Rect;
 #endif
                }
        }
index c2801d3..ed6f67f 100644 (file)
@@ -99,6 +99,11 @@ namespace Xamarin.Forms.Core.UITests
                        App.Tap(q => q.Raw(ViewQuery));
                }
 
+               public void TouchAndHoldView()
+               {
+                       App.TouchAndHold(q => q.Raw(ViewQuery));
+               }
+
                public void DismissPopOver()
                {
                        App.Screenshot("About to dismiss pop over");
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs
new file mode 100644 (file)
index 0000000..9e8e51b
--- /dev/null
@@ -0,0 +1,156 @@
+using System.Threading;
+using NUnit.Framework;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.Core.UITests
+{
+       [TestFixture]
+       [Category(UITestCategories.Image)]
+       internal class ImageButtonUITests : _ViewUITests
+       {
+               public ImageButtonUITests()
+               {
+                       PlatformViewType = Views.ImageButton;
+               }
+
+               protected override void NavigateToGallery()
+               {
+                       App.NavigateToGallery(GalleryQueries.ImageButtonGallery);
+
+                       // let remote images load
+                       Thread.Sleep(2000);
+               }
+
+               [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+               public override void _Focus()
+               {
+               }
+
+               // TODO
+               public override void _GestureRecognizers()
+               {
+               }
+
+               [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+               public override void _IsFocused()
+               {
+               }
+
+               [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+               public override void _UnFocus()
+               {
+               }
+
+               // TODO
+               // Implement control specific ui tests
+
+               // TODO
+               // Tests for remote images
+
+               protected override void FixtureTeardown()
+               {
+                       App.NavigateBack();
+                       base.FixtureTeardown();
+               }
+
+
+
+
+               // ImageButton Tests
+               [Test]
+               [UiTest(typeof(ImageButton), "BorderColor")]
+               [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+               [UiTestBroken(BrokenReason.CalabashiOSUnsupported, "iOS nil result")]
+               public void BorderColor()
+               {
+                       //TODO iOS
+                       var remote = new ViewContainerRemote(App, Test.ImageButton.BorderColor, PlatformViewType);
+                       remote.GoTo();
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "BorderWidth")]
+               [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+               public void BorderWidth()
+               {
+                       var remote = new ViewContainerRemote(App, Test.ImageButton.BorderWidth, PlatformViewType);
+                       remote.GoTo();
+
+#if __IOS__
+                       var borderWidth = remote.GetProperty<float>(ImageButton.BorderWidthProperty);
+                       Assert.AreEqual(15.0f, borderWidth);
+#endif
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "Clicked")]
+               public void Clicked()
+               {
+                       var remote = new EventViewContainerRemote(App, Test.ImageButton.Clicked, PlatformViewType);
+                       remote.GoTo();
+
+                       var textBeforeClick = remote.GetEventLabel().Text;
+                       Assert.AreEqual("Event: Clicked (none)", textBeforeClick);
+
+                       // Click ImageButton
+                       remote.TapView();
+
+                       var textAfterClick = remote.GetEventLabel().Text;
+                       Assert.AreEqual("Event: Clicked (fired 1)", textAfterClick);
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "Pressed")]
+               public void Pressed()
+               {
+                       var remote = new EventViewContainerRemote(App, Test.ImageButton.Pressed, PlatformViewType);
+                       remote.GoTo();
+
+                       var textBeforeClick = remote.GetEventLabel().Text;
+                       Assert.AreEqual("Event: Pressed (none)", textBeforeClick);
+
+                       // Press ImageButton
+                       remote.TouchAndHoldView();
+
+                       var textAfterClick = remote.GetEventLabel().Text;
+                       Assert.AreEqual("Event: Pressed (fired 1)", textAfterClick);
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "Command")]
+               public void Command()
+               {
+                       var remote = new ViewContainerRemote(App, Test.ImageButton.Command, PlatformViewType);
+                       remote.GoTo();
+
+                       remote.TapView();
+
+                       App.WaitForElement(q => q.Marked("Hello Command"));
+                       App.Tap(q => q.Marked("Destroy"));
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "CornerRadius")]
+               [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+               public void CornerRadius()
+               {
+                       var remote = new ViewContainerRemote(App, Test.ImageButton.CornerRadius, PlatformViewType);
+                       remote.GoTo();
+
+#if __IOS__
+            var cornerRadius = remote.GetProperty<float>(ImageButton.CornerRadiusProperty);
+            Assert.AreEqual(20.0f, cornerRadius);
+#endif
+               }
+
+               [Test]
+               [UiTest(typeof(ImageButton), "Image")]
+               [UiTestExempt(ExemptReason.TimeConsuming, "Need way to check Android resources")]
+               public void Image()
+               {
+                       //TODO iOS
+                       var remote = new ViewContainerRemote(App, Test.ImageButton.Image, PlatformViewType);
+                       remote.GoTo();
+               }
+       }
+}
\ No newline at end of file
index 50dd42f..39ce124 100644 (file)
@@ -29,6 +29,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)Tests\EditorUITests.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Tests\EntryUITests.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Tests\FrameUITests.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Tests\ImageButtonUITests.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Tests\ImageUITests.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Tests\LabelUITests.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Tests\Legacy-CellsUITests.cs" />
diff --git a/Xamarin.Forms.Core.UnitTests/ImageButtonUnitTest.cs b/Xamarin.Forms.Core.UnitTests/ImageButtonUnitTest.cs
new file mode 100644 (file)
index 0000000..116cc30
--- /dev/null
@@ -0,0 +1,419 @@
+using System;
+using NUnit.Framework;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+       [TestFixture]
+       public class ImageButtonTests
+               : CommandSourceTests<ImageButton>
+       {
+               [SetUp]
+               public override void Setup()
+               {
+                       base.Setup();
+                       Device.PlatformServices = new MockPlatformServices(getStreamAsync: GetStreamAsync);
+               }
+
+               [TearDown]
+               public override void TearDown()
+               {
+                       base.TearDown();
+                       Device.PlatformServices = null;
+               }
+
+               [Test]
+               public void TestSizing()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       var result = image.GetSizeRequest(double.PositiveInfinity, double.PositiveInfinity);
+
+                       Assert.AreEqual(100, result.Request.Width);
+                       Assert.AreEqual(20, result.Request.Height);
+               }
+
+               [Test]
+               public void TestAspectSizingWithConstrainedHeight()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+                       Assert.AreEqual(50, result.Request.Width);
+                       Assert.AreEqual(10, result.Request.Height);
+               }
+
+               [Test]
+               public void TestAspectSizingWithConstrainedWidth()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+                       Assert.AreEqual(25, result.Request.Width);
+                       Assert.AreEqual(5, result.Request.Height);
+               }
+
+               [Test]
+               public void TestAspectFillSizingWithConstrainedHeight()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       image.Aspect = Aspect.AspectFill;
+                       var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+                       Assert.AreEqual(50, result.Request.Width);
+                       Assert.AreEqual(10, result.Request.Height);
+               }
+
+               [Test]
+               public void TestAspectFillSizingWithConstrainedWidth()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       image.Aspect = Aspect.AspectFill;
+                       var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+                       Assert.AreEqual(25, result.Request.Width);
+                       Assert.AreEqual(5, result.Request.Height);
+               }
+
+               [Test]
+               public void TestFillSizingWithConstrainedHeight()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       image.Aspect = Aspect.AspectFill;
+                       var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+                       Assert.AreEqual(50, result.Request.Width);
+                       Assert.AreEqual(10, result.Request.Height);
+               }
+
+               [Test]
+               public void TestFillSizingWithConstrainedWidth()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+                       image.Aspect = Aspect.AspectFill;
+                       var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+                       Assert.AreEqual(25, result.Request.Width);
+                       Assert.AreEqual(5, result.Request.Height);
+               }
+
+               [Test]
+               public void TestSizeChanged()
+               {
+                       var image = new ImageButton { Source = "File0.png" };
+                       Assert.AreEqual("File0.png", ((FileImageSource)image.Source).File);
+
+                       var preferredSizeChanged = false;
+                       image.MeasureInvalidated += (sender, args) => preferredSizeChanged = true;
+
+                       image.Source = "File1.png";
+                       Assert.AreEqual("File1.png", ((FileImageSource)image.Source).File);
+                       Assert.True(preferredSizeChanged);
+               }
+
+               [Test]
+               public void TestSource()
+               {
+                       var image = new ImageButton();
+
+                       Assert.IsNull(image.Source);
+
+                       bool signaled = false;
+                       image.PropertyChanged += (sender, e) =>
+                       {
+                               if (e.PropertyName == "Source")
+                                       signaled = true;
+                       };
+
+                       var source = ImageSource.FromFile("File.png");
+                       image.Source = source;
+
+                       Assert.AreEqual(source, image.Source);
+                       Assert.True(signaled);
+               }
+
+               [Test]
+               public void TestSourceDoubleSet()
+               {
+                       var image = new ImageButton { Source = ImageSource.FromFile("File.png") };
+
+                       bool signaled = false;
+                       image.PropertyChanged += (sender, e) =>
+                       {
+                               if (e.PropertyName == "Source")
+                                       signaled = true;
+                       };
+
+                       image.Source = image.Source;
+
+                       Assert.False(signaled);
+               }
+
+               [Test]
+               public void TestFileImageSourceChanged()
+               {
+                       var source = (FileImageSource)ImageSource.FromFile("File.png");
+
+                       bool signaled = false;
+                       source.SourceChanged += (sender, e) =>
+                       {
+                               signaled = true;
+                       };
+
+                       source.File = "Other.png";
+                       Assert.AreEqual("Other.png", source.File);
+
+                       Assert.True(signaled);
+               }
+
+               [Test]
+               public void TestFileImageSourcePropertiesChangedTriggerResize()
+               {
+                       var source = new FileImageSource();
+                       var image = new ImageButton { Source = source };
+                       bool fired = false;
+                       image.MeasureInvalidated += (sender, e) => fired = true;
+                       Assert.Null(source.File);
+                       source.File = "foo.png";
+                       Assert.NotNull(source.File);
+                       Assert.True(fired);
+               }
+
+               [Test]
+               public void TestStreamImageSourcePropertiesChangedTriggerResize()
+               {
+                       var source = new StreamImageSource();
+                       var image = new ImageButton { Source = source };
+                       bool fired = false;
+                       image.MeasureInvalidated += (sender, e) => fired = true;
+                       Assert.Null(source.Stream);
+                       source.Stream = token => Task.FromResult<Stream>(null);
+                       Assert.NotNull(source.Stream);
+                       Assert.True(fired);
+               }
+
+               [Test]
+               public void TestImageSourceToNullCancelsLoading()
+               {
+                       var image = new ImageButton();
+                       var mockImageRenderer = new MockImageRenderer(image);
+                       var loader = new UriImageSource { Uri = new Uri("http://www.public-domain-image.com/free-images/miscellaneous/big-high-border-fence.jpg") };
+                       image.Source = loader;
+                       Assert.IsTrue(image.IsLoading);
+                       image.Source = null;
+                       Assert.IsFalse(image.IsLoading);
+                       Assert.IsTrue(cancelled);
+               }
+
+               static bool cancelled;
+
+               static async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken)
+               {
+                       try
+                       {
+                               await Task.Delay(5000, cancellationToken);
+                       }
+                       catch (TaskCanceledException ex)
+                       {
+                               cancelled = true;
+                               throw ex;
+                       }
+
+                       if (cancellationToken.IsCancellationRequested)
+                       {
+                               cancelled = true;
+                               throw new TaskCanceledException();
+                       }
+
+                       var stream = typeof(ImageTests).Assembly.GetManifestResourceStream(uri.LocalPath.Substring(1));
+                       return stream;
+               }
+
+               class MockImageRenderer
+               {
+                       public MockImageRenderer(ImageButton element)
+                       {
+                               Element = element;
+                               Element.PropertyChanged += (sender, e) =>
+                               {
+                                       if (e.PropertyName == nameof(ImageButton.Source))
+                                               Load();
+                               };
+                       }
+
+                       public ImageButton Element { get; set; }
+
+                       public async void Load()
+                       {
+                               if (initialLoad && Element.Source != null)
+                               {
+                                       initialLoad = false;
+                                       Element.SetIsLoading(true);
+                                       await (Element.Source as UriImageSource).GetStreamAsync();
+                                       Element.SetIsLoading(false);
+                               }
+                       }
+
+                       bool initialLoad = true;
+               }
+
+               [Test]
+               [TestCase(true)]
+               [TestCase(false)]
+               public void TestClickedvent(bool isEnabled)
+               {
+                       var view = new ImageButton()
+                       {
+                               IsEnabled = isEnabled,
+                       };
+
+                       bool activated = false;
+                       view.Clicked += (sender, e) => activated = true;
+
+                       ((IButtonController)view).SendClicked();
+
+                       Assert.True(activated == isEnabled ? true : false);
+               }
+
+               [Test]
+               [TestCase(true)]
+               [TestCase(false)]
+               public void TestPressedEvent(bool isEnabled)
+               {
+                       var view = new ImageButton()
+                       {
+                               IsEnabled = isEnabled,
+                       };
+
+                       bool pressed = false;
+                       view.Pressed += (sender, e) => pressed = true;
+
+                       ((IButtonController)view).SendPressed();
+
+                       Assert.True(pressed == isEnabled ? true : false);
+               }
+
+               [Test]
+               [TestCase(true)]
+               [TestCase(false)]
+               public void TestReleasedEvent(bool isEnabled)
+               {
+                       var view = new ImageButton()
+                       {
+                               IsEnabled = isEnabled,
+                       };
+
+                       bool released = false;
+                       view.Released += (sender, e) => released = true;
+
+                       ((IButtonController)view).SendReleased();
+
+                       Assert.True(released == isEnabled ? true : false);
+               }
+
+               protected override ImageButton CreateSource()
+               {
+                       return new ImageButton();
+               }
+
+               protected override void Activate(ImageButton source)
+               {
+                       ((IButtonController)source).SendClicked();
+               }
+
+               protected override BindableProperty IsEnabledProperty
+               {
+                       get { return ImageButton.IsEnabledProperty; }
+               }
+
+               protected override BindableProperty CommandProperty
+               {
+                       get { return ImageButton.CommandProperty; }
+               }
+
+               protected override BindableProperty CommandParameterProperty
+               {
+                       get { return ImageButton.CommandParameterProperty; }
+               }
+
+
+               [Test]
+               public void TestBindingContextPropagation()
+               {
+                       var context = new object();
+                       var button = new ImageButton();
+                       button.BindingContext = context;
+                       var source = new FileImageSource();
+                       button.Source = source;
+                       Assert.AreSame(context, source.BindingContext);
+
+                       button = new ImageButton();
+                       source = new FileImageSource();
+                       button.Source = source;
+                       button.BindingContext = context;
+                       Assert.AreSame(context, source.BindingContext);
+               }
+
+               [Test]
+               public void TestImageSourcePropertiesChangedTriggerResize()
+               {
+                       var source = new FileImageSource();
+                       var button = new ImageButton { Source = source };
+                       bool fired = false;
+                       button.MeasureInvalidated += (sender, e) => fired = true;
+                       Assert.Null(source.File);
+                       source.File = "foo.png";
+                       Assert.NotNull(source.File);
+                       Assert.True(fired);
+               }
+
+
+               [Test]
+               public void CommandCanExecuteUpdatesEnabled()
+               {
+                       var button = new ImageButton();
+
+                       bool result = false;
+
+                       var bindingContext = new
+                       {
+                               Command = new Command(() => { }, () => result)
+                       };
+
+                       button.SetBinding(ImageButton.CommandProperty, "Command");
+                       button.BindingContext = bindingContext;
+
+                       Assert.False(button.IsEnabled);
+
+                       result = true;
+
+                       bindingContext.Command.ChangeCanExecute();
+
+                       Assert.True(button.IsEnabled);
+               }
+
+               [Test]
+               public void ButtonClickWhenCommandCanExecuteFalse()
+               {
+                       bool invoked = false;
+                       var button = new ImageButton()
+                       {
+                               Command = new Command(() => invoked = true
+                               , () => false),
+                       };
+
+                       (button as IButtonController)
+                               ?.SendClicked();
+
+                       Assert.False(invoked);
+               }
+       }
+}
index 70d193a..f05810c 100644 (file)
@@ -76,6 +76,7 @@
     <Compile Include="DependencyResolutionTests.cs" />
     <Compile Include="EffectiveFlowDirectionExtensions.cs" />
     <Compile Include="FlowDirectionTests.cs" />
+    <Compile Include="ImageButtonUnitTest.cs" />
     <Compile Include="LayoutChildIntoBoundingRegionTests.cs" />
     <Compile Include="MenuUnitTests.cs" />
     <Compile Include="RegionTests.cs" />
index 8a44a33..af7f746 100644 (file)
@@ -8,16 +8,16 @@ using Xamarin.Forms.Platform;
 namespace Xamarin.Forms
 {
        [RenderWith(typeof(_ButtonRenderer))]
-       public class Button : View, IFontElement, ITextElement, IBorderElement, IButtonController, IElementConfiguration<Button>, IPaddingElement
+       public class Button : View, IFontElement, ITextElement, IBorderElement, IButtonController, IElementConfiguration<Button>, IPaddingElement, IBorderController, IImageController, IViewController
        {
                const double DefaultSpacing = 10;
                const int DefaultBorderRadius = 5;
                const int DefaultCornerRadius = -1;
 
-               public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(Button), null, propertyChanged: (bo, o, n) => ((Button)bo).OnCommandChanged());
+               public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(Button), null, propertyChanging: OnCommandChanging, propertyChanged: OnCommandChanged);
 
-               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 CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(Button), null,
+                       propertyChanged: (bindable, oldvalue, newvalue) => ButtonElementManager.CommandCanExecuteChanged(bindable, EventArgs.Empty));
 
                public static readonly BindableProperty ContentLayoutProperty =
                        BindableProperty.Create("ContentLayout", typeof(ButtonContentLayout), typeof(Button), new ButtonContentLayout(ButtonContentLayout.ImagePosition.Left, DefaultSpacing));
@@ -46,28 +46,34 @@ namespace Xamarin.Forms
                public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create("CornerRadius", typeof(int), typeof(Button), defaultValue: DefaultCornerRadius,
                        propertyChanged: CornerRadiusPropertyChanged);
 
-               public static readonly BindableProperty ImageProperty = BindableProperty.Create("Image", typeof(FileImageSource), typeof(Button), default(FileImageSource),
-                       propertyChanging: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue),
-                       propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue));
+               public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(FileImageSource), typeof(Button), default(FileImageSource),
+                       propertyChanging: OnImageSourceChanging,
+                       propertyChanged: OnImageSourceChanged);
+
 
                public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
 
                public Thickness Padding
                {
-                       get { return (Thickness)GetValue (PaddingElement.PaddingProperty); }
-                       set { SetValue (PaddingElement.PaddingProperty, value); }
+                       get { return (Thickness)GetValue(PaddingElement.PaddingProperty); }
+                       set { SetValue(PaddingElement.PaddingProperty, value); }
                }
 
-               Thickness IPaddingElement.PaddingDefaultValueCreator ()
+               Thickness IPaddingElement.PaddingDefaultValueCreator()
                {
-                       return default (Thickness);
+                       return default(Thickness);
                }
 
-               void IPaddingElement.OnPaddingPropertyChanged (Thickness oldValue, Thickness newValue)
+               void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
                {
                        InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
                }
 
+
+               internal static readonly BindablePropertyKey IsPressedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsPressed), typeof(bool), typeof(Button), default(bool));
+               public static readonly BindableProperty IsPressedProperty = IsPressedPropertyKey.BindableProperty;
+
+
                readonly Lazy<PlatformConfigurationRegistry<Button>> _platformConfigurationRegistry;
 
                public Color BorderColor
@@ -137,38 +143,33 @@ namespace Xamarin.Forms
                        set { SetValue(TextElement.TextColorProperty, value); }
                }
 
-               bool IsEnabledCore
+               bool IButtonController.IsEnabledCore
                {
                        set { SetValueCore(IsEnabledProperty, value); }
                }
 
                [EditorBrowsable(EditorBrowsableState.Never)]
-               public void SendClicked()
-               {
-                       if (IsEnabled == true)
-                       {
-                               Command?.Execute(CommandParameter);
-                               Clicked?.Invoke(this, EventArgs.Empty);
-                       }
-               }
+               public void SendClicked() => ButtonElementManager.ElementClicked(this, this);
+
+               public bool IsPressed => (bool)GetValue(IsPressedProperty);
 
                [EditorBrowsable(EditorBrowsableState.Never)]
-               public void SendPressed()
-               {
-                       if (IsEnabled == true)
-                       {
-                               Pressed?.Invoke(this, EventArgs.Empty); 
-                       }
-               }
+               void IButtonController.SetIsPressed(bool isPressed) => SetValue(IsPressedPropertyKey, isPressed);
 
                [EditorBrowsable(EditorBrowsableState.Never)]
-               public void SendReleased()
-               {
-                       if (IsEnabled == true)
-                       {
-                               Released?.Invoke(this, EventArgs.Empty);
-                       }
-               }
+               public void SendPressed() => ButtonElementManager.ElementPressed(this, this);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SendReleased() => ButtonElementManager.ElementReleased(this, this);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               void IButtonController.PropagateUpClicked() => Clicked?.Invoke(this, EventArgs.Empty);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               void IButtonController.PropagateUpPressed() => Pressed?.Invoke(this, EventArgs.Empty);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               void IButtonController.PropagateUpReleased() => Released?.Invoke(this, EventArgs.Empty);
 
                public FontAttributes FontAttributes
                {
@@ -190,6 +191,9 @@ namespace Xamarin.Forms
                }
 
                public event EventHandler Clicked;
+               BindableProperty IBorderController.CornerRadiusProperty => Button.CornerRadiusProperty;
+               BindableProperty IBorderController.BorderColorProperty => Button.BorderColorProperty;
+               BindableProperty IBorderController.BorderWidthProperty => Button.BorderWidthProperty;
 
                public event EventHandler Pressed;
 
@@ -205,6 +209,18 @@ namespace Xamarin.Forms
                        return _platformConfigurationRegistry.Value.On<T>();
                }
 
+               protected internal override void ChangeVisualState()
+               {
+                       if (IsEnabled && IsPressed)
+                       {
+                               VisualStateManager.GoToState(this, ButtonElementManager.PressedVisualState);
+                       }
+                       else
+                       {
+                               base.ChangeVisualState();
+                       }
+               }
+
                protected override void OnBindingContextChanged()
                {
                        FileImageSource image = Image;
@@ -214,25 +230,6 @@ namespace Xamarin.Forms
                        base.OnBindingContextChanged();
                }
 
-               protected override void OnPropertyChanging(string propertyName = null)
-               {
-                       if (propertyName == CommandProperty.PropertyName)
-                       {
-                               ICommand cmd = Command;
-                               if (cmd != null)
-                                       cmd.CanExecuteChanged -= CommandCanExecuteChanged;
-                       }
-
-                       base.OnPropertyChanging(propertyName);
-               }
-
-               void CommandCanExecuteChanged(object sender, EventArgs eventArgs)
-               {
-                       ICommand cmd = Command;
-                       if (cmd != null)
-                               IsEnabledCore = cmd.CanExecute(CommandParameter);
-               }
-
                void IFontElement.OnFontFamilyChanged(string oldValue, string newValue) =>
                        InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
 
@@ -248,38 +245,17 @@ namespace Xamarin.Forms
                void IFontElement.OnFontChanged(Font oldValue, Font newValue) =>
                        InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
 
-               void OnCommandChanged()
-               {
-                       if (Command != null)
-                       {
-                               Command.CanExecuteChanged += CommandCanExecuteChanged;
-                               CommandCanExecuteChanged(this, EventArgs.Empty);
-                       }
-                       else
-                               IsEnabledCore = true;
-               }
+               Aspect IImageController.Aspect => Aspect.AspectFit;
+               ImageSource IImageController.Source => Image;
+               bool IImageController.IsOpaque => false;
 
-               void OnSourceChanged(object sender, EventArgs eventArgs)
-               {
-                       OnPropertyChanged(ImageProperty.PropertyName);
-                       InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
-               }
+               BindableProperty IImageController.SourceProperty => ImageProperty;
+               BindableProperty IImageController.AspectProperty => null;
+               BindableProperty IImageController.IsOpaqueProperty => null;
 
-               void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
-               {
-                       if (newvalue != null)
-                       {
-                               newvalue.SourceChanged += OnSourceChanged;
-                               SetInheritedBindingContext(newvalue, BindingContext);
-                       }
-                       InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
-               }
 
-               void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
-               {
-                       if (oldvalue != null)
-                               oldvalue.SourceChanged -= OnSourceChanged;
-               }
+               void IImageController.RaiseImageSourcePropertyChanged() => OnPropertyChanged(ImageProperty.PropertyName);
+
 
                /// <summary>
                /// Flag to prevent overwriting the value of CornerRadius
@@ -338,6 +314,58 @@ namespace Xamarin.Forms
                {
                }
 
+               void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+                       ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+               static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
+               {
+                       ImageSource newSource = (ImageSource)newValue;
+                       Button button = (Button)bindable;
+                       if (newSource != null)
+                       {
+                               newSource.SourceChanged += button.OnImageSourcesSourceChanged;
+                       }
+                       ImageElementManager.ImageSourceChanged(bindable, newSource);
+               }
+
+               static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
+               {
+                       ImageSource oldSource = (ImageSource)oldValue;
+                       Button button = (Button)bindable;
+
+                       if (oldSource != null)
+                       {
+                               oldSource.SourceChanged -= button.OnImageSourcesSourceChanged;
+                       }
+                       ImageElementManager.ImageSourceChanging(oldSource);
+               }
+
+
+               void OnCommandCanExecuteChanged(object sender, EventArgs e) =>
+                       ButtonElementManager.CommandCanExecuteChanged(this, EventArgs.Empty);
+
+               static void OnCommandChanged(BindableObject bo, object o, object n)
+               {
+                       var button = (Button)bo;
+                       if (n is ICommand newCommand)
+                               newCommand.CanExecuteChanged += button.OnCommandCanExecuteChanged;
+
+                       ButtonElementManager.CommandChanged(button);
+               }
+
+               static void OnCommandChanging(BindableObject bo, object o, object n)
+               {
+                       var button = (Button)bo;
+                       if (o != null)
+                       {
+                               (o as ICommand).CanExecuteChanged -= button.OnCommandCanExecuteChanged;
+                       }
+               }
+
+               void IImageController.SetIsLoading(bool isLoading)
+               {
+               }
+
                [DebuggerDisplay("Image Position = {Position}, Spacing = {Spacing}")]
                [TypeConverter(typeof(ButtonContentTypeConverter))]
                public sealed class ButtonContentLayout
diff --git a/Xamarin.Forms.Core/ButtonElementManager.cs b/Xamarin.Forms.Core/ButtonElementManager.cs
new file mode 100644 (file)
index 0000000..5474d90
--- /dev/null
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+       internal static class ButtonElementManager
+       {
+               public const string PressedVisualState = "Pressed";
+
+               public static void CommandChanged(IButtonController sender)
+               {
+                       if (sender.Command != null)
+                       {
+                               CommandCanExecuteChanged(sender, EventArgs.Empty);
+                       }
+                       else
+                       {
+                               sender.IsEnabledCore = true;
+                       }
+               }
+
+               public static void CommandCanExecuteChanged(object sender, EventArgs e)
+               {
+                       IButtonController ButtonElementManager = (IButtonController)sender;
+                       ICommand cmd = ButtonElementManager.Command;
+                       if (cmd != null)
+                       {
+                               ButtonElementManager.IsEnabledCore = cmd.CanExecute(ButtonElementManager.CommandParameter);
+                       }
+               }
+
+
+               public static void ElementClicked(VisualElement visualElement, IButtonController ButtonElementManager)
+               {
+                       if (visualElement.IsEnabled == true)
+                       {
+                               ButtonElementManager.Command?.Execute(ButtonElementManager.CommandParameter);
+                               ButtonElementManager.PropagateUpClicked();
+                       }
+               }
+
+               public static void ElementPressed(VisualElement visualElement, IButtonController ButtonElementManager)
+               {
+                       if (visualElement.IsEnabled == true)
+                       {
+                               ButtonElementManager.SetIsPressed(true);
+                               visualElement.ChangeVisualStateInternal();
+                               ButtonElementManager.PropagateUpPressed();
+                       }
+               }
+
+               public static void ElementReleased(VisualElement visualElement, IButtonController ButtonElementManager)
+               {
+                       if (visualElement.IsEnabled == true)
+                       {
+                               ButtonElementManager.SetIsPressed(false);
+                               visualElement.ChangeVisualStateInternal();
+                               ButtonElementManager.PropagateUpReleased();
+                       }
+               }
+
+
+       }
+}
diff --git a/Xamarin.Forms.Core/IBorderController.cs b/Xamarin.Forms.Core/IBorderController.cs
new file mode 100644 (file)
index 0000000..ab1727b
--- /dev/null
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
+
+namespace Xamarin.Forms
+{
+       public interface IBorderController : INotifyPropertyChanged
+       {
+               BindableProperty CornerRadiusProperty { get; }
+               BindableProperty BorderColorProperty { get; }
+               BindableProperty BorderWidthProperty { get; }
+               int CornerRadius { get; }
+               Color BorderColor { get; }
+               Color BackgroundColor { get; }
+               double BorderWidth { get; }
+               bool IsSet(BindableProperty targetProperty);
+       }
+}
index b7e3bc4..6ec308d 100644 (file)
@@ -1,3 +1,6 @@
+using System;
+using System.Windows.Input;
+
 namespace Xamarin.Forms
 {
        public interface IButtonController : IViewController
@@ -5,5 +8,14 @@ namespace Xamarin.Forms
                void SendClicked();
                void SendPressed();
                void SendReleased();
+               object CommandParameter { get; set; }
+               ICommand Command { get; set; }
+               bool IsEnabledCore { set; }
+               void PropagateUpClicked();
+               void PropagateUpPressed();
+               void PropagateUpReleased();
+               bool IsPressed { get; }
+               void SetIsPressed(bool isPressed);
+
        }
 }
\ No newline at end of file
index 8dce107..ad80022 100644 (file)
@@ -1,7 +1,16 @@
+using System;
+
 namespace Xamarin.Forms
 {
-       public interface IImageController
+       public interface IImageController : IViewController
        {
                void SetIsLoading(bool isLoading);
+               Aspect Aspect { get; }
+               ImageSource Source { get; }
+               bool IsOpaque { get; }
+               void RaiseImageSourcePropertyChanged();
+               BindableProperty SourceProperty { get; }
+               BindableProperty AspectProperty { get; }
+               BindableProperty IsOpaqueProperty { get; }
        }
 }
\ No newline at end of file
index 1dcbe1c..11566f2 100644 (file)
@@ -8,16 +8,16 @@ using Xamarin.Forms.Platform;
 namespace Xamarin.Forms
 {
        [RenderWith(typeof(_ImageRenderer))]
-       public class Image : View, IImageController, IElementConfiguration<Image>
+       public class Image : View, IImageController, IElementConfiguration<Image>, IViewController
        {
-               public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource), 
-                       propertyChanging: OnSourcePropertyChanging, propertyChanged: OnSourcePropertyChanged);
+               public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ImageSource), typeof(Image), default(ImageSource),
+                       propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
 
-               public static readonly BindableProperty AspectProperty = BindableProperty.Create("Aspect", typeof(Aspect), typeof(Image), Aspect.AspectFit);
+               public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(Image), Aspect.AspectFit);
 
-               public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create("IsOpaque", typeof(bool), typeof(Image), false);
+               public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create(nameof(IsOpaque), typeof(bool), typeof(Image), false);
 
-               internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly("IsLoading", typeof(bool), typeof(Image), default(bool));
+               internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoading), typeof(bool), typeof(Image), default(bool));
 
                public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty;
 
@@ -54,9 +54,7 @@ namespace Xamarin.Forms
 
                protected override void OnBindingContextChanged()
                {
-                       if (Source != null)
-                               SetInheritedBindingContext(Source, BindingContext);
-
+                       ImageElementManager.OnBindingContextChanged(this, this);
                        base.OnBindingContextChanged();
                }
 
@@ -64,112 +62,50 @@ namespace Xamarin.Forms
                protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
                {
                        SizeRequest desiredSize = base.OnSizeRequest(double.PositiveInfinity, double.PositiveInfinity);
-
-                       double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height;
-                       double constraintAspect = widthConstraint / heightConstraint;
-
-                       double desiredWidth = desiredSize.Request.Width;
-                       double desiredHeight = desiredSize.Request.Height;
-
-                       if (desiredWidth == 0 || desiredHeight == 0)
-                               return new SizeRequest(new Size(0, 0));
-
-                       double width = desiredWidth;
-                       double height = desiredHeight;
-                       if (constraintAspect > desiredAspect)
-                       {
-                               // constraint area is proportionally wider than image
-                               switch (Aspect)
-                               {
-                                       case Aspect.AspectFit:
-                                       case Aspect.AspectFill:
-                                               height = Math.Min(desiredHeight, heightConstraint);
-                                               width = desiredWidth * (height / desiredHeight);
-                                               break;
-                                       case Aspect.Fill:
-                                               width = Math.Min(desiredWidth, widthConstraint);
-                                               height = desiredHeight * (width / desiredWidth);
-                                               break;
-                               }
-                       }
-                       else if (constraintAspect < desiredAspect)
-                       {
-                               // constraint area is proportionally taller than image
-                               switch (Aspect)
-                               {
-                                       case Aspect.AspectFit:
-                                       case Aspect.AspectFill:
-                                               width = Math.Min(desiredWidth, widthConstraint);
-                                               height = desiredHeight * (width / desiredWidth);
-                                               break;
-                                       case Aspect.Fill:
-                                               height = Math.Min(desiredHeight, heightConstraint);
-                                               width = desiredWidth * (height / desiredHeight);
-                                               break;
-                               }
-                       }
-                       else
-                       {
-                               // constraint area is same aspect as image
-                               width = Math.Min(desiredWidth, widthConstraint);
-                               height = desiredHeight * (width / desiredWidth);
-                       }
-
-                       return new SizeRequest(new Size(width, height));
+                       return ImageElementManager.Measure(this, desiredSize, widthConstraint, heightConstraint);
                }
 
-               void OnSourceChanged(object sender, EventArgs eventArgs)
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SetIsLoading(bool isLoading)
                {
-                       OnPropertyChanged(SourceProperty.PropertyName);
-                       InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+                       SetValue(IsLoadingPropertyKey, isLoading);
                }
 
-               static void OnSourcePropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+               public IPlatformElementConfiguration<T, Image> On<T>() where T : IConfigPlatform
                {
-                       ((Image)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue);
+                       return _platformConfigurationRegistry.Value.On<T>();
                }
 
-               void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
+               BindableProperty IImageController.SourceProperty => SourceProperty;
+               BindableProperty IImageController.AspectProperty => AspectProperty;
+               BindableProperty IImageController.IsOpaqueProperty => IsOpaqueProperty;
+
+               void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+                       ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+               static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
                {
-                       if (newvalue != null)
+                       ImageSource newSource = (ImageSource)newValue;
+                       Image image = (Image)bindable;
+                       if (newSource != null)
                        {
-                               newvalue.SourceChanged += OnSourceChanged;
-                               SetInheritedBindingContext(newvalue, BindingContext);
+                               newSource.SourceChanged += image.OnImageSourcesSourceChanged;
                        }
-
-                       InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+                       ImageElementManager.ImageSourceChanged(bindable, newSource);
                }
 
-               static void OnSourcePropertyChanging(BindableObject bindable, object oldvalue, object newvalue)
+               static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
                {
-                       ((Image)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue);
-               }
+                       ImageSource oldSource = (ImageSource)oldValue;
+                       Image image = (Image)bindable;
 
-               async void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
-               {
-                       if (oldvalue == null)
-                               return;
-                       
-                       oldvalue.SourceChanged -= OnSourceChanged;
-                       try
+                       if (oldSource != null)
                        {
-                               await oldvalue.Cancel();
-                       }
-                       catch(ObjectDisposedException)
-                       { 
-                               // Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
+                               oldSource.SourceChanged -= image.OnImageSourcesSourceChanged;
                        }
+                       ImageElementManager.ImageSourceChanging(oldSource);
                }
 
-               [EditorBrowsable(EditorBrowsableState.Never)]
-               public void SetIsLoading(bool isLoading)
-               {
-                       SetValue(IsLoadingPropertyKey, isLoading);
-               }
-
-               public IPlatformElementConfiguration<T, Image> On<T>() where T : IConfigPlatform
-               {
-                       return _platformConfigurationRegistry.Value.On<T>();
-               }
+               void IImageController.RaiseImageSourcePropertyChanged() => OnPropertyChanged(nameof(Source));
        }
 }
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/ImageButton.cs b/Xamarin.Forms.Core/ImageButton.cs
new file mode 100644 (file)
index 0000000..41774da
--- /dev/null
@@ -0,0 +1,247 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reflection;
+using System.Windows.Input;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+       [RenderWith(typeof(_ImageButtonRenderer))]
+       public class ImageButton : View, IImageController, IElementConfiguration<ImageButton>, IBorderElement, IButtonController, IBorderController, IViewController, IPaddingElement
+       {
+               const int DefaultCornerRadius = -1;
+
+               public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(Button), null, propertyChanging: OnCommandChanging, propertyChanged: OnCommandChanged);
+
+               public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(int), typeof(Button), defaultValue: DefaultCornerRadius);
+
+               public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ImageButton), null,
+                       propertyChanged: (bindable, oldvalue, newvalue) => ButtonElementManager.CommandCanExecuteChanged(bindable, EventArgs.Empty));
+
+               public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create(nameof(BorderWidth), typeof(double), typeof(Button), -1d);
+
+               public static readonly BindableProperty BorderColorProperty = BorderElement.BorderColorProperty;
+
+               public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ImageSource), typeof(ImageButton), default(ImageSource),
+                       propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
+
+               public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(ImageButton), Aspect.AspectFit);
+
+               public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create(nameof(IsOpaque), typeof(bool), typeof(ImageButton), false);
+
+               internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoading), typeof(bool), typeof(ImageButton), default(bool));
+
+               public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty;
+
+               internal static readonly BindablePropertyKey IsPressedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsPressed), typeof(bool), typeof(ImageButton), default(bool));
+
+               public static readonly BindableProperty IsPressedProperty = IsPressedPropertyKey.BindableProperty;
+
+               public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
+               public event EventHandler Clicked;
+               public event EventHandler Pressed;
+               public event EventHandler Released;
+
+               readonly Lazy<PlatformConfigurationRegistry<ImageButton>> _platformConfigurationRegistry;
+
+
+               public ImageButton()
+               {
+                       _platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<ImageButton>>(() => new PlatformConfigurationRegistry<ImageButton>(this));
+               }
+
+               public Color BorderColor
+               {
+                       get { return (Color)GetValue(BorderElement.BorderColorProperty); }
+                       set { SetValue(BorderElement.BorderColorProperty, value); }
+               }
+
+               public int CornerRadius
+               {
+                       get { return (int)GetValue(CornerRadiusProperty); }
+                       set { SetValue(CornerRadiusProperty, value); }
+               }
+
+               public double BorderWidth
+               {
+                       get { return (double)GetValue(BorderWidthProperty); }
+                       set { SetValue(BorderWidthProperty, value); }
+               }
+
+               public Aspect Aspect
+               {
+                       get { return (Aspect)GetValue(AspectProperty); }
+                       set { SetValue(AspectProperty, value); }
+               }
+
+               public bool IsLoading => (bool)GetValue(IsLoadingProperty);
+
+               public bool IsPressed => (bool)GetValue(IsPressedProperty);
+
+               public bool IsOpaque
+               {
+                       get { return (bool)GetValue(IsOpaqueProperty); }
+                       set { SetValue(IsOpaqueProperty, value); }
+               }
+               public ICommand Command
+               {
+                       get { return (ICommand)GetValue(CommandProperty); }
+                       set { SetValue(CommandProperty, value); }
+               }
+
+               public object CommandParameter
+               {
+                       get { return GetValue(CommandParameterProperty); }
+                       set { SetValue(CommandParameterProperty, value); }
+               }
+
+               [TypeConverter(typeof(ImageSourceConverter))]
+               public ImageSource Source
+               {
+                       get { return (ImageSource)GetValue(SourceProperty); }
+                       set { SetValue(SourceProperty, value); }
+               }
+
+               bool IButtonController.IsEnabledCore
+               {
+                       set { SetValueCore(IsEnabledProperty, value); }
+               }
+
+               protected override void OnBindingContextChanged()
+               {
+                       ImageElementManager.OnBindingContextChanged(this, this);
+                       base.OnBindingContextChanged();
+               }
+
+               protected internal override void ChangeVisualState()
+               {
+                       if (IsEnabled && IsPressed)
+                       {
+                               VisualStateManager.GoToState(this, ButtonElementManager.PressedVisualState);
+                       }
+                       else
+                       {
+                               base.ChangeVisualState();
+                       }
+               }
+
+               protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+               {
+                       SizeRequest desiredSize = base.OnMeasure(double.PositiveInfinity, double.PositiveInfinity);
+                       return ImageElementManager.Measure(this, desiredSize, widthConstraint, heightConstraint);
+               }
+
+               public IPlatformElementConfiguration<T, ImageButton> On<T>() where T : IConfigPlatform => _platformConfigurationRegistry.Value.On<T>();
+
+               void IBorderElement.OnBorderColorPropertyChanged(Color oldValue, Color newValue)
+               {
+               }
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SetIsLoading(bool isLoading) => SetValue(IsLoadingPropertyKey, isLoading);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SetIsPressed(bool isPressed) =>
+                       SetValue(IsPressedPropertyKey, isPressed);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SendClicked() =>
+                       ButtonElementManager.ElementClicked(this, this);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SendPressed() =>
+                       ButtonElementManager.ElementPressed(this, this);
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SendReleased() =>
+                       ButtonElementManager.ElementReleased(this, this);
+
+               public void PropagateUpClicked() =>
+                       Clicked?.Invoke(this, EventArgs.Empty);
+
+               public void PropagateUpPressed() =>
+                       Pressed?.Invoke(this, EventArgs.Empty);
+
+               public void PropagateUpReleased() =>
+                       Released?.Invoke(this, EventArgs.Empty);
+
+               public void RaiseImageSourcePropertyChanged() =>
+                       OnPropertyChanged(nameof(Source));
+
+               public Thickness Padding
+               {
+                       get { return (Thickness)GetValue(PaddingElement.PaddingProperty); }
+                       set { SetValue(PaddingElement.PaddingProperty, value); }
+               }
+
+               Thickness IPaddingElement.PaddingDefaultValueCreator()
+               {
+                       return default(Thickness);
+               }
+
+               void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
+               {
+                       InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+               }
+               BindableProperty IBorderController.CornerRadiusProperty => ImageButton.CornerRadiusProperty;
+
+               BindableProperty IBorderController.BorderColorProperty => ImageButton.BorderColorProperty;
+
+               BindableProperty IBorderController.BorderWidthProperty => ImageButton.BorderWidthProperty;
+
+               BindableProperty IImageController.SourceProperty => SourceProperty;
+
+               BindableProperty IImageController.AspectProperty => AspectProperty;
+
+               BindableProperty IImageController.IsOpaqueProperty => IsOpaqueProperty;
+
+               void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+                       ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+               static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
+               {
+                       ImageSource newSource = (ImageSource)newValue;
+                       ImageButton button = (ImageButton)bindable;
+                       if (newSource != null)
+                       {
+                               newSource.SourceChanged += button.OnImageSourcesSourceChanged;
+                       }
+                       ImageElementManager.ImageSourceChanged(bindable, newSource);
+               }
+
+               static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
+               {
+                       ImageSource oldSource = (ImageSource)oldValue;
+                       ImageButton button = (ImageButton)bindable;
+
+                       if (oldSource != null)
+                       {
+                               oldSource.SourceChanged -= button.OnImageSourcesSourceChanged;
+                       }
+                       ImageElementManager.ImageSourceChanging(oldSource);
+               }
+
+               void OnCommandCanExecuteChanged(object sender, EventArgs e) =>
+                       ButtonElementManager.CommandCanExecuteChanged(this, EventArgs.Empty);
+
+               static void OnCommandChanged(BindableObject bo, object o, object n)
+               {
+                       var button = (ImageButton)bo;
+                       if (n is ICommand newCommand)
+                               newCommand.CanExecuteChanged += button.OnCommandCanExecuteChanged;
+
+                       ButtonElementManager.CommandChanged(button);
+               }
+
+               static void OnCommandChanging(BindableObject bo, object o, object n)
+               {
+                       var button = (ImageButton)bo;
+                       if (o != null)
+                       {
+                               (o as ICommand).CanExecuteChanged -= button.OnCommandCanExecuteChanged;
+                       }
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/ImageElementManager.cs b/Xamarin.Forms.Core/ImageElementManager.cs
new file mode 100644 (file)
index 0000000..babd5bb
--- /dev/null
@@ -0,0 +1,103 @@
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+
+       internal static class ImageElementManager
+       {
+               public static SizeRequest Measure(
+                       IImageController ImageElementManager,
+                       SizeRequest desiredSize,
+                       double widthConstraint,
+                       double heightConstraint)
+               {
+                       double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height;
+                       double constraintAspect = widthConstraint / heightConstraint;
+
+                       double desiredWidth = desiredSize.Request.Width;
+                       double desiredHeight = desiredSize.Request.Height;
+
+                       if (desiredWidth == 0 || desiredHeight == 0)
+                               return new SizeRequest(new Size(0, 0));
+
+                       double width = desiredWidth;
+                       double height = desiredHeight;
+                       if (constraintAspect > desiredAspect)
+                       {
+                               // constraint area is proportionally wider than image
+                               switch (ImageElementManager.Aspect)
+                               {
+                                       case Aspect.AspectFit:
+                                       case Aspect.AspectFill:
+                                               height = Math.Min(desiredHeight, heightConstraint);
+                                               width = desiredWidth * (height / desiredHeight);
+                                               break;
+                                       case Aspect.Fill:
+                                               width = Math.Min(desiredWidth, widthConstraint);
+                                               height = desiredHeight * (width / desiredWidth);
+                                               break;
+                               }
+                       }
+                       else if (constraintAspect < desiredAspect)
+                       {
+                               // constraint area is proportionally taller than image
+                               switch (ImageElementManager.Aspect)
+                               {
+                                       case Aspect.AspectFit:
+                                       case Aspect.AspectFill:
+                                               width = Math.Min(desiredWidth, widthConstraint);
+                                               height = desiredHeight * (width / desiredWidth);
+                                               break;
+                                       case Aspect.Fill:
+                                               height = Math.Min(desiredHeight, heightConstraint);
+                                               width = desiredWidth * (height / desiredHeight);
+                                               break;
+                               }
+                       }
+                       else
+                       {
+                               // constraint area is same aspect as image
+                               width = Math.Min(desiredWidth, widthConstraint);
+                               height = desiredHeight * (width / desiredWidth);
+                       }
+
+                       return new SizeRequest(new Size(width, height));
+               }
+
+               internal static void OnBindingContextChanged(IImageController image, VisualElement visualElement)
+               {
+                       if (image.Source != null)
+                               BindableObject.SetInheritedBindingContext(image.Source, visualElement?.BindingContext);
+               }
+
+
+               public static async void ImageSourceChanging(ImageSource oldImageSource)
+               {
+                       if (oldImageSource == null) return;
+                       try
+                       {
+                               await oldImageSource.Cancel().ConfigureAwait(false);
+                       }
+                       catch (ObjectDisposedException)
+                       {
+                               // Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
+                       }
+               }
+
+               public static void ImageSourceChanged(BindableObject bindable, ImageSource newSource)
+               {
+                       var visualElement = (VisualElement)bindable;
+                       if (newSource != null)
+                               BindableObject.SetInheritedBindingContext(newSource, visualElement?.BindingContext);
+
+                       visualElement?.InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+               }
+
+               public static void ImageSourcesSourceChanged(object sender, EventArgs e)
+               {
+                       ((IImageController)sender).RaiseImageSourcePropertyChanged();
+                       ((VisualElement)sender).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/PlatformConfiguration/AndroidSpecific/ImageButton.cs b/Xamarin.Forms.Core/PlatformConfiguration/AndroidSpecific/ImageButton.cs
new file mode 100644 (file)
index 0000000..e3f0ec0
--- /dev/null
@@ -0,0 +1,101 @@
+namespace Xamarin.Forms.PlatformConfiguration.AndroidSpecific
+{
+       using FormsImageButton = Forms.ImageButton;
+
+       public static class ImageButton
+       {
+               #region Shadow
+               public static readonly BindableProperty IsShadowEnabledProperty = BindableProperty.Create("IsShadowEnabled", typeof(bool), typeof(Forms.ImageButton), false);
+
+               public static bool GetIsShadowEnabled(BindableObject element)
+               {
+                       return (bool)element.GetValue(IsShadowEnabledProperty);
+               }
+
+               public static void SetIsShadowEnabled(BindableObject element, bool value)
+               {
+                       element.SetValue(IsShadowEnabledProperty, value);
+               }
+
+               public static bool GetIsShadowEnabled(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+               {
+                       return GetIsShadowEnabled(config.Element);
+               }
+
+               public static IPlatformElementConfiguration<Android, FormsImageButton> SetIsShadowEnabled(this IPlatformElementConfiguration<Android, FormsImageButton> config, bool value)
+               {
+                       SetIsShadowEnabled(config.Element, value);
+                       return config;
+               }
+
+               public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create("ShadowColor", typeof(Color), typeof(ImageButton), Color.Default);
+
+               public static Color GetShadowColor(BindableObject element)
+               {
+                       return (Color)element.GetValue(ShadowColorProperty);
+               }
+
+               public static void SetShadowColor(BindableObject element, Color value)
+               {
+                       element.SetValue(ShadowColorProperty, value);
+               }
+
+               public static Color GetShadowColor(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+               {
+                       return GetShadowColor(config.Element);
+               }
+
+               public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowColor(this IPlatformElementConfiguration<Android, FormsImageButton> config, Color value)
+               {
+                       SetShadowColor(config.Element, value);
+                       return config;
+               }
+
+               public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create("ShadowRadius", typeof(double), typeof(ImageButton), 10.0);
+
+               public static double GetShadowRadius(BindableObject element)
+               {
+                       return (double)element.GetValue(ShadowRadiusProperty);
+               }
+
+               public static void SetShadowRadius(BindableObject element, double value)
+               {
+                       element.SetValue(ShadowRadiusProperty, value);
+               }
+
+               public static double GetShadowRadius(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+               {
+                       return GetShadowRadius(config.Element);
+               }
+
+               public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowRadius(this IPlatformElementConfiguration<Android, FormsImageButton> config, double value)
+               {
+                       SetShadowRadius(config.Element, value);
+                       return config;
+               }
+
+               public static readonly BindableProperty ShadowOffsetProperty = BindableProperty.Create("ShadowOffset", typeof(Size), typeof(VisualElement), Size.Zero);
+
+               public static Size GetShadowOffset(BindableObject element)
+               {
+                       return (Size)element.GetValue(ShadowOffsetProperty);
+               }
+
+               public static void SetShadowOffset(BindableObject element, Size value)
+               {
+                       element.SetValue(ShadowOffsetProperty, value);
+               }
+
+               public static Size GetShadowOffset(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+               {
+                       return GetShadowOffset(config.Element);
+               }
+
+               public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowOffset(this IPlatformElementConfiguration<Android, FormsImageButton> config, Size value)
+               {
+                       SetShadowOffset(config.Element, value);
+                       return config;
+               }
+               #endregion
+       }
+}
index a2b250e..5e141c3 100644 (file)
@@ -357,17 +357,20 @@ namespace Xamarin.Forms
                        set { SetValue(RotationYProperty, value); }
                }
 
-               public double Scale {
+               public double Scale
+               {
                        get => (double)GetValue(ScaleProperty);
                        set => SetValue(ScaleProperty, value);
                }
 
-               public double ScaleX {
+               public double ScaleX
+               {
                        get => (double)GetValue(ScaleXProperty);
                        set => SetValue(ScaleXProperty, value);
                }
 
-               public double ScaleY {
+               public double ScaleY
+               {
                        get => (double)GetValue(ScaleYProperty);
                        set => SetValue(ScaleYProperty, value);
                }
@@ -911,6 +914,9 @@ namespace Xamarin.Forms
                                focus(this, new FocusEventArgs(this, true));
                }
 
+               internal void ChangeVisualStateInternal() => ChangeVisualState();
+
+
                protected internal virtual void ChangeVisualState()
                {
                        if (!IsEnabled)
index 5abd22d..570141a 100644 (file)
@@ -3,21 +3,21 @@ using System.Diagnostics;
 
 namespace Xamarin.Forms.CustomAttributes
 {
-       [Conditional ("DEBUG")]
-       [AttributeUsage (
+       [Conditional("DEBUG")]
+       [AttributeUsage(
                AttributeTargets.Class |
-               AttributeTargets.Event | 
+               AttributeTargets.Event |
                AttributeTargets.Property |
                AttributeTargets.Method |
-               AttributeTargets.Delegate, 
+               AttributeTargets.Delegate,
                AllowMultiple = true
                )]
        public class PlatformAttribute : Attribute
        {
                readonly string _platform;
-               public PlatformAttribute (object platform)
+               public PlatformAttribute(object platform)
                {
-                       _platform = platform.ToString ();
+                       _platform = platform.ToString();
                }
 
                public string Platform => "Issue: " + _platform;
@@ -25,7 +25,7 @@ namespace Xamarin.Forms.CustomAttributes
 
        public enum IssueTracker
        {
-               Github, 
+               Github,
                Bugzilla,
                None
        }
@@ -38,8 +38,8 @@ namespace Xamarin.Forms.CustomAttributes
                Default
        }
 
-       [Conditional ("DEBUG")]
-       [AttributeUsage (
+       [Conditional("DEBUG")]
+       [AttributeUsage(
                AttributeTargets.Class |
                AttributeTargets.Method,
                AllowMultiple = true
@@ -48,7 +48,7 @@ namespace Xamarin.Forms.CustomAttributes
        {
                bool _modal;
 
-               public IssueAttribute (IssueTracker issueTracker, int issueNumber, string description, 
+               public IssueAttribute(IssueTracker issueTracker, int issueNumber, string description,
                        NavigationBehavior navigationBehavior = NavigationBehavior.Default, int issueTestNumber = 0)
                {
                        IssueTracker = issueTracker;
@@ -59,8 +59,8 @@ namespace Xamarin.Forms.CustomAttributes
                        IssueTestNumber = issueTestNumber;
                }
 
-               public IssueAttribute (IssueTracker issueTracker, int issueNumber, string description, 
-                       PlatformAffected platformsAffected, NavigationBehavior navigationBehavior = NavigationBehavior.Default, 
+               public IssueAttribute(IssueTracker issueTracker, int issueNumber, string description,
+                       PlatformAffected platformsAffected, NavigationBehavior navigationBehavior = NavigationBehavior.Default,
                        int issueTestNumber = 0)
                {
                        IssueTracker = issueTracker;
@@ -88,16 +88,16 @@ namespace Xamarin.Forms.CustomAttributes
                        : $"{IssueTracker.ToString().Substring(0, 1)}{IssueNumber} ({IssueTestNumber})";
        }
 
-       [Conditional ("DEBUG")]
+       [Conditional("DEBUG")]
        public class UiTestExemptAttribute : Attribute
        {
                // optional string reason
                readonly string _exemptReason;
                readonly string _description;
 
-               public UiTestExemptAttribute (ExemptReason exemptReason, string description = null)
+               public UiTestExemptAttribute(ExemptReason exemptReason, string description = null)
                {
-                       _exemptReason = Enum.GetName (typeof(ExemptReason), exemptReason);
+                       _exemptReason = Enum.GetName(typeof(ExemptReason), exemptReason);
                        _description = description;
                }
 
@@ -106,13 +106,13 @@ namespace Xamarin.Forms.CustomAttributes
                public string Description => "Description: " + _description;
        }
 
-       [Conditional ("DEBUG")]
+       [Conditional("DEBUG")]
        public class UiTestFragileAttribute : Attribute
        {
                // optional string reason
                readonly string _description;
 
-               public UiTestFragileAttribute (string description = null)
+               public UiTestFragileAttribute(string description = null)
                {
                        _description = description;
                }
@@ -120,17 +120,17 @@ namespace Xamarin.Forms.CustomAttributes
                public string Description => "Description: " + _description;
        }
 
-       [Conditional ("DEBUG")]
-       [AttributeUsage (AttributeTargets.All, AllowMultiple = true)]
+       [Conditional("DEBUG")]
+       [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
        public class UiTestBrokenAttribute : Attribute
        {
                // optional string reason
                readonly string _exemptReason;
                readonly string _description;
 
-               public UiTestBrokenAttribute (BrokenReason exemptReason, string description = null)
+               public UiTestBrokenAttribute(BrokenReason exemptReason, string description = null)
                {
-                       _exemptReason = Enum.GetName (typeof(ExemptReason), exemptReason);
+                       _exemptReason = Enum.GetName(typeof(ExemptReason), exemptReason);
                        _description = description;
                }
 
@@ -164,7 +164,7 @@ namespace Xamarin.Forms.CustomAttributes
                CannotTest
        }
 
-       public enum BrokenReason 
+       public enum BrokenReason
        {
                UITestBug,
                CalabashBug,
@@ -228,7 +228,8 @@ namespace Xamarin.Forms.CustomAttributes
                        BorderColor,
                        BorderRadius,
                        Image,
-                       Padding
+                       Padding,
+                       Pressed
                }
 
                public enum VisualElement
@@ -547,6 +548,24 @@ namespace Xamarin.Forms.CustomAttributes
                        Fill
                }
 
+               public enum ImageButton
+               {
+                       Source,
+                       Aspect,
+                       IsOpaque,
+                       IsLoading,
+                       AspectFill,
+                       AspectFit,
+                       Fill,
+                       BorderColor,
+                       CornerRadius,
+                       BorderWidth,
+                       Clicked,
+                       Command,
+                       Image,
+                       Pressed
+               }
+
                public enum ImageSource
                {
                        FromFile,
@@ -603,7 +622,8 @@ namespace Xamarin.Forms.CustomAttributes
                        MaxLines
                }
 
-               public enum MasterDetailPage {
+               public enum MasterDetailPage
+               {
                        Master,
                        Detail,
                        IsGestureEnabled,
@@ -611,23 +631,27 @@ namespace Xamarin.Forms.CustomAttributes
                        MasterBehavior
                }
 
-               public enum OpenGlView {
+               public enum OpenGlView
+               {
                        OnDisplay,
                        HasRenderLoop,
                        Display
                }
 
-               public enum ProgressBar {
+               public enum ProgressBar
+               {
                        Progress,
                        ProgressColor
                }
 
-               public enum RelativeLayout {
+               public enum RelativeLayout
+               {
                        Children,
                        SetBoundsConstraint
                }
 
-               public enum ScrollView {
+               public enum ScrollView
+               {
                        ContentSize,
                        Orientation,
                        Content
@@ -654,7 +678,8 @@ namespace Xamarin.Forms.CustomAttributes
                        PlaceholderColor
                }
 
-               public enum Slider {
+               public enum Slider
+               {
                        Minimum,
                        Maximum,
                        Value,
@@ -664,23 +689,27 @@ namespace Xamarin.Forms.CustomAttributes
                        ThumbImage
                }
 
-               public enum StackLayout {
+               public enum StackLayout
+               {
                        Orientation,
                        Spacing
                }
 
-               public enum Stepper {
+               public enum Stepper
+               {
                        Maximum,
                        Minimum,
                        Value,
                        Increment
                }
 
-               public enum Switch {
+               public enum Switch
+               {
                        IsToggled
                }
 
-               public enum TimePicker {
+               public enum TimePicker
+               {
                        Format,
                        Time,
                        Focus,
@@ -690,7 +719,8 @@ namespace Xamarin.Forms.CustomAttributes
                        FontSize
                }
 
-               public enum WebView {
+               public enum WebView
+               {
                        UrlWebViewSource,
                        HtmlWebViewSource,
                        LoadHtml,
@@ -700,16 +730,19 @@ namespace Xamarin.Forms.CustomAttributes
                        EvaluateJavaScript
                }
 
-               public enum UrlWebViewSource {
+               public enum UrlWebViewSource
+               {
                        Url
                }
 
-               public enum HtmlWebViewSource {
+               public enum HtmlWebViewSource
+               {
                        BaseUrl,
                        Html
                }
 
-               public enum Grid {
+               public enum Grid
+               {
                        Children,
                        SetRow,
                        SetRowSpan,
@@ -721,11 +754,13 @@ namespace Xamarin.Forms.CustomAttributes
                        RowDefinitions
                }
 
-               public enum ContentPage {
+               public enum ContentPage
+               {
                        Content
                }
 
-               public enum Picker {
+               public enum Picker
+               {
                        Title,
                        Items,
                        SelectedIndex,
@@ -736,27 +771,32 @@ namespace Xamarin.Forms.CustomAttributes
                        FontSize
                }
 
-               public enum FileImageSource {
+               public enum FileImageSource
+               {
                        File,
                        Cancel
                }
 
-               public enum StreamImageSource {
+               public enum StreamImageSource
+               {
                        Stream
                }
 
-               public enum OnPlatform {
+               public enum OnPlatform
+               {
                        WinPhone,
                        Android,
                        iOS
                }
 
-               public enum OnIdiom {
+               public enum OnIdiom
+               {
                        Phone,
                        Tablet
                }
 
-               public enum Span {
+               public enum Span
+               {
                        Text,
                        ForeGroundColor,
                        BackgroundColor,
@@ -764,7 +804,8 @@ namespace Xamarin.Forms.CustomAttributes
                        PropertyChanged
                }
 
-               public enum FormattedString {
+               public enum FormattedString
+               {
                        ToStringOverride,
                        Spans,
                        PropertyChanged
index 4d50e0f..f8a08c0 100644 (file)
@@ -4,20 +4,19 @@ using Android.Content;
 using Android.Graphics;
 using Android.Support.V7.Widget;
 using Android.Util;
-using Xamarin.Forms.Internals;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using GlobalResource = Android.Resource;
 using Object = Java.Lang.Object;
 using AView = Android.Views.View;
 using AMotionEvent = Android.Views.MotionEvent;
 using AMotionEventActions = Android.Views.MotionEventActions;
 using static System.String;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
 
 namespace Xamarin.Forms.Platform.Android.AppCompat
 {
-       public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener
+       public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener, IBorderVisualElementRenderer
        {
-               ButtonBackgroundTracker _backgroundTracker;
+               BorderBackgroundManager _backgroundTracker;
                TextColorSwitcher _textColorSwitcher;
                float _defaultFontSize;
                Thickness? _defaultPadding;
@@ -25,16 +24,21 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                bool _isDisposed;
                int _imageHeight = -1;
                Thickness _paddingDeltaPix = new Thickness();
+               IVisualElementRenderer _visualElementRenderer;
 
                public ButtonRenderer(Context context) : base(context)
                {
                        AutoPackage = false;
+                       _visualElementRenderer = this;
+                       _backgroundTracker = new BorderBackgroundManager(this);
                }
 
                [Obsolete("This constructor is obsolete as of version 2.5. Please use ButtonRenderer(Context) instead.")]
                public ButtonRenderer()
                {
                        AutoPackage = false;
+                       _visualElementRenderer = this;
+                       _backgroundTracker = new BorderBackgroundManager(this);
                }
 
                global::Android.Widget.Button NativeButton => Control;
@@ -96,6 +100,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                                }
                                _backgroundTracker?.Dispose();
                                _backgroundTracker = null;
+                               _visualElementRenderer = null;
                        }
 
                        base.Dispose(disposing);
@@ -105,10 +110,6 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                {
                        base.OnElementChanged(e);
 
-                       if (e.OldElement != null)
-                       {
-                       }
-
                        if (e.NewElement != null)
                        {
                                if (Control == null)
@@ -126,10 +127,6 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                                        button.AddOnAttachStateChangeListener(this);
                                }
 
-                               if (_backgroundTracker == null)
-                                       _backgroundTracker = new ButtonBackgroundTracker(Element, Control);
-                               else
-                                       _backgroundTracker.Button = e.NewElement;
 
                                _defaultFontSize = 0f;
                                _defaultPadding = null;
@@ -312,7 +309,22 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                void UpdateContentEdge(Thickness? delta = null)
                {
                        _paddingDeltaPix = delta ?? new Thickness();
-                       UpdatePadding();
+                       UpdatePadding();                        
+               }
+
+               float IBorderVisualElementRenderer.ShadowRadius => Control.ShadowRadius;
+               float IBorderVisualElementRenderer.ShadowDx => Control.ShadowDx;
+               float IBorderVisualElementRenderer.ShadowDy => Control.ShadowDy;
+               AColor IBorderVisualElementRenderer.ShadowColor => Control.ShadowColor;
+               bool IBorderVisualElementRenderer.UseDefaultPadding() => Element.OnThisPlatform().UseDefaultPadding();
+               bool IBorderVisualElementRenderer.UseDefaultShadow() => Element.OnThisPlatform().UseDefaultShadow();
+               bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+               VisualElement IBorderVisualElementRenderer.Element => Element;
+               AView IBorderVisualElementRenderer.View => Control;
+               event EventHandler<VisualElementChangedEventArgs> IBorderVisualElementRenderer.ElementChanged
+               {
+                       add => _visualElementRenderer.ElementChanged += value;
+                       remove => _visualElementRenderer.ElementChanged -= value;
                }
 
                class ButtonClickListener : Object, AView.IOnClickListener
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ImageButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ImageButtonRenderer.cs
new file mode 100644 (file)
index 0000000..70410e6
--- /dev/null
@@ -0,0 +1,308 @@
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Support.V7.Widget;
+using AView = Android.Views.View;
+using Android.Views;
+using Xamarin.Forms.Internals;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Android.Graphics.Drawables;
+using Android.Graphics;
+using Xamarin.Forms.Platform.Android.FastRenderers;
+using Android.OS;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       public class ImageButtonRenderer :
+               AppCompatImageButton,
+               IVisualElementRenderer,
+               IBorderVisualElementRenderer,
+               IImageRendererController,
+               AView.IOnFocusChangeListener,
+               AView.IOnClickListener,
+               AView.IOnTouchListener
+       {
+               bool _inputTransparent;
+               bool _disposed;
+               bool _skipInvalidate;
+               int? _defaultLabelFor;
+               VisualElementTracker _tracker;
+               VisualElementRenderer _visualElementRenderer;
+               BorderBackgroundManager _backgroundTracker;
+               IPlatformElementConfiguration<PlatformConfiguration.Android, ImageButton> _platformElementConfiguration;
+               private ImageButton _imageButton;
+
+               public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+               public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+
+               void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
+               VisualElement IVisualElementRenderer.Element => ImageButton;
+               AView IVisualElementRenderer.View => this;
+               ViewGroup IVisualElementRenderer.ViewGroup => null;
+               VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
+
+               ImageButton ImageButton
+               {
+                       get => _imageButton;
+                       set
+                       {
+                               _imageButton = value;
+                               _platformElementConfiguration = null;
+                       }
+               }
+
+               void IImageRendererController.SkipInvalidate() => _skipInvalidate = true;
+               bool IImageRendererController.IsDisposed => _disposed;
+
+               AppCompatImageButton Control => this;
+               public ImageButtonRenderer(Context context) : base(context)
+               {
+                       // These set the defaults so visually it matches up with other platforms
+                       SetPadding(0, 0, 0, 0);
+                       SoundEffectsEnabled = false;
+                       SetOnClickListener(this);
+                       SetOnTouchListener(this);
+                       OnFocusChangeListener = this;
+
+                       Tag = this;
+                       _backgroundTracker = new BorderBackgroundManager(this, false);
+               }
+
+               protected override void Dispose(bool disposing)
+               {
+                       if (_disposed)
+                               return;
+
+                       _disposed = true;
+
+                       if (disposing)
+                       {
+
+                               ImageElementManager.Dispose(this);
+
+                               _tracker?.Dispose();
+                               _tracker = null;
+
+                               _backgroundTracker?.Dispose();
+                               _backgroundTracker = null;
+
+                               if (ImageButton != null)
+                               {
+                                       ImageButton.PropertyChanged -= OnElementPropertyChanged;
+
+                                       if (Android.Platform.GetRenderer(ImageButton) == this)
+                                       {
+                                               ImageButton.ClearValue(Android.Platform.RendererProperty);
+                                       }
+
+                                       ImageButton = null;
+                               }
+                       }
+
+                       base.Dispose(disposing);
+               }
+
+               public override void Invalidate()
+               {
+                       if (_skipInvalidate)
+                       {
+                               _skipInvalidate = false;
+                               return;
+                       }
+
+                       base.Invalidate();
+               }
+
+               Size MinimumSize()
+               {
+                       return new Size();
+               }
+
+               SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+               {
+                       if (_disposed)
+                       {
+                               return new SizeRequest();
+                       }
+                       Measure(widthConstraint, heightConstraint);
+                       return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
+               }
+
+               void IVisualElementRenderer.SetElement(VisualElement element)
+               {
+
+                       if (element == null)
+                       {
+                               throw new ArgumentNullException(nameof(element));
+                       }
+
+                       if (!(element is ImageButton image))
+                       {
+                               throw new ArgumentException("Element is not of type " + typeof(ImageButton), nameof(element));
+                       }
+
+                       ImageButton oldElement = ImageButton;
+                       ImageButton = image;
+
+                       Performance.Start(out string reference);
+
+                       if (oldElement != null)
+                       {
+                               oldElement.PropertyChanged -= OnElementPropertyChanged;
+                       }
+
+                       element.PropertyChanged += OnElementPropertyChanged;
+
+                       if (_tracker == null)
+                       {
+                               _tracker = new VisualElementTracker(this);
+                               ImageElementManager.Init(this);
+
+                       }
+
+                       if (_visualElementRenderer == null)
+                       {
+                               _visualElementRenderer = new VisualElementRenderer(this);
+                       }
+
+                       Performance.Stop(reference);
+                       this.EnsureId();
+
+                       UpdateInputTransparent();
+                       UpdatePadding();
+
+                       ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(oldElement, ImageButton));
+                       ImageButton?.SendViewInitialized(Control);
+               }
+
+               public override void Draw(Canvas canvas)
+               {
+                       if (ImageButton == null)
+                               return;
+
+                       var backgroundDrawable = _backgroundTracker?.BackgroundDrawable;
+
+                       RectF drawableBounds = null;
+
+                       if ((int)Build.VERSION.SdkInt >= 18 && backgroundDrawable != null)
+                       {
+                               var outlineBounds = backgroundDrawable.GetPaddingBounds(canvas.Width, canvas.Height);
+                               var width = (float)MeasuredWidth;
+                               var height = (float)MeasuredHeight;
+
+                               var widthRatio = 1f;
+                               var heightRatio = 1f;
+
+                               if (ImageButton.Aspect == Aspect.AspectFill && OnThisPlatform().GetIsShadowEnabled())
+                                       Internals.Log.Warning(nameof(ImageButtonRenderer), "AspectFill isn't fully supported when using shadows. Image may be clipped incorrectly to Border");
+
+                               switch (ImageButton.Aspect)
+                               {
+                                       case Aspect.Fill:
+                                               break;
+                                       case Aspect.AspectFill:
+                                       case Aspect.AspectFit:
+                                               heightRatio = (float)Drawable.IntrinsicHeight / height;
+                                               widthRatio = (float)Drawable.IntrinsicWidth / width;
+                                               break;
+                               }
+
+                               drawableBounds = new RectF(outlineBounds.Left * widthRatio, outlineBounds.Top * heightRatio, outlineBounds.Right * widthRatio, outlineBounds.Bottom * heightRatio);
+                       }
+
+                       if (drawableBounds != null)
+                               Drawable.SetBounds((int)drawableBounds.Left, (int)drawableBounds.Top, (int)drawableBounds.Right, (int)drawableBounds.Bottom);
+
+
+
+                       base.Draw(canvas);
+                       if (_backgroundTracker.BackgroundDrawable != null)
+                               _backgroundTracker.BackgroundDrawable.DrawOutline(canvas, canvas.Width, canvas.Height);
+               }
+
+               void IVisualElementRenderer.SetLabelFor(int? id)
+               {
+                       if (_defaultLabelFor == null)
+                               _defaultLabelFor = LabelFor;
+
+                       LabelFor = (int)(id ?? _defaultLabelFor);
+               }
+
+               public override bool OnTouchEvent(MotionEvent e)
+               {
+                       if (!Enabled || (_inputTransparent && Enabled))
+                               return false;
+
+                       return base.OnTouchEvent(e);
+               }
+
+
+               void UpdatePadding()
+               {
+                       SetPadding(
+                               (int)(Context.ToPixels(ImageButton.Padding.Left)),
+                               (int)(Context.ToPixels(ImageButton.Padding.Top)),
+                               (int)(Context.ToPixels(ImageButton.Padding.Right)),
+                               (int)(Context.ToPixels(ImageButton.Padding.Bottom))
+                       );
+               }
+
+               void UpdateInputTransparent()
+               {
+                       if (ImageButton == null || _disposed)
+                       {
+                               return;
+                       }
+
+                       _inputTransparent = ImageButton.InputTransparent;
+               }
+
+               // Image related
+               void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+                               UpdateInputTransparent();
+                       else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+                               UpdatePadding();
+
+                       ElementPropertyChanged?.Invoke(this, e);
+               }
+
+
+               // general state related
+               void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
+               {
+                       ((IElementController)ImageButton).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
+               }
+               // general state related
+
+
+               // Button related
+               void IOnClickListener.OnClick(AView v) =>
+                       ButtonElementManager.OnClick(ImageButton, ImageButton, v);
+
+               bool IOnTouchListener.OnTouch(AView v, MotionEvent e) =>
+                       ButtonElementManager.OnTouch(ImageButton, ImageButton, v, e);
+               // Button related
+
+
+               float IBorderVisualElementRenderer.ShadowRadius => Context.ToPixels(OnThisPlatform().GetShadowRadius());
+               float IBorderVisualElementRenderer.ShadowDx => Context.ToPixels(OnThisPlatform().GetShadowOffset().Width);
+               float IBorderVisualElementRenderer.ShadowDy => Context.ToPixels(OnThisPlatform().GetShadowOffset().Height);
+               AColor IBorderVisualElementRenderer.ShadowColor => OnThisPlatform().GetShadowColor().ToAndroid();
+               bool IBorderVisualElementRenderer.IsShadowEnabled() => OnThisPlatform().GetIsShadowEnabled();
+               bool IBorderVisualElementRenderer.UseDefaultPadding() => false;
+               bool IBorderVisualElementRenderer.UseDefaultShadow() => false;
+               VisualElement IBorderVisualElementRenderer.Element => ImageButton;
+               AView IBorderVisualElementRenderer.View => this;
+
+               IPlatformElementConfiguration<PlatformConfiguration.Android, ImageButton> OnThisPlatform()
+               {
+                       if (_platformElementConfiguration == null)
+                               _platformElementConfiguration = ImageButton.OnThisPlatform();
+
+                       return _platformElementConfiguration;
+               }
+       }
+}
diff --git a/Xamarin.Forms.Platform.Android/BackgroundManager.cs b/Xamarin.Forms.Platform.Android/BackgroundManager.cs
new file mode 100644 (file)
index 0000000..bc023f6
--- /dev/null
@@ -0,0 +1,63 @@
+using System;
+using System.ComponentModel;
+using Xamarin.Forms.Internals;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       internal static class BackgroundManager
+       {
+               public static void Init(IVisualElementRenderer renderer)
+               {
+                       _ = renderer ?? throw new ArgumentNullException($"{nameof(BackgroundManager)}.{nameof(Init)} {nameof(renderer)} cannot be null");
+
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ElementChanged += OnElementChanged;
+               }
+
+               public static void Dispose(IVisualElementRenderer renderer)
+               {
+                       _ = renderer ?? throw new ArgumentNullException($"{nameof(BackgroundManager)}.{nameof(Init)} {nameof(renderer)} cannot be null");
+
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       renderer.ElementChanged -= OnElementChanged;
+               }
+
+               static void UpdateBackgroundColor(AView Control, VisualElement Element, Color? color = null)
+               {
+                       if (Element == null || Control == null)
+                               return;
+
+                       Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid());
+               }
+
+
+               static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       Performance.Start(out string reference);
+                       if (e.OldElement != null)
+                       {
+                               e.OldElement.PropertyChanged -= OnElementPropertyChanged;
+                       }
+
+                       if (e.NewElement != null)
+                       {
+                               var renderer = (sender as IVisualElementRenderer);
+                               e.NewElement.PropertyChanged += OnElementPropertyChanged;
+                               UpdateBackgroundColor(renderer?.View, renderer?.Element);
+                       }
+
+                       Performance.Stop(reference);
+               }
+
+
+               static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+                       {
+                               var renderer = (sender as IVisualElementRenderer);
+                               UpdateBackgroundColor(renderer?.View, renderer?.Element);
+                       }
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/BorderBackgroundManager.cs b/Xamarin.Forms.Platform.Android/BorderBackgroundManager.cs
new file mode 100644 (file)
index 0000000..d9c1937
--- /dev/null
@@ -0,0 +1,229 @@
+using System;
+using System.ComponentModel;
+using Android.Content.Res;
+using AView = Android.Views.View;
+using Android.Graphics.Drawables;
+using Android.OS;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Specifics = Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using AButton = Android.Widget.Button;
+using AColor = Android.Graphics.Color;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       internal class BorderBackgroundManager : IDisposable
+       {
+               Drawable _defaultDrawable;
+               BorderDrawable _backgroundDrawable;
+               RippleDrawable _rippleDrawable;
+               bool _drawableEnabled;
+               bool _disposed;
+               IBorderVisualElementRenderer _renderer;
+               VisualElement Element => _renderer?.Element;
+               AView Control => _renderer?.View;
+               readonly bool _drawOutlineWithBackground;
+
+               public bool DrawOutlineWithBackground { get; set; } = true;
+               public BorderDrawable BackgroundDrawable => _backgroundDrawable;
+
+               public BorderBackgroundManager(IBorderVisualElementRenderer renderer) : this(renderer, true)
+               {
+               }
+
+               public BorderBackgroundManager(IBorderVisualElementRenderer renderer, bool drawOutlineWithBackground)
+               {
+                       _renderer = renderer;
+                       _renderer.ElementChanged += OnElementChanged;
+                       _drawOutlineWithBackground = drawOutlineWithBackground;
+               }
+
+               void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       if (e.OldElement != null)
+                       {
+                               (e.OldElement as IBorderController).PropertyChanged -= BorderElementPropertyChanged;
+                       }
+
+                       if (e.NewElement != null)
+                       {
+                               if (BorderElement != null)
+                               {
+                                       BorderElement.PropertyChanged -= BorderElementPropertyChanged;
+                               }
+                               BorderElement = (IBorderController)e.NewElement;
+                               BorderElement.PropertyChanged += BorderElementPropertyChanged;
+                       }
+
+                       Reset();
+                       UpdateDrawable();
+               }
+
+               public IBorderController BorderElement
+               {
+                       get;
+                       private set;
+               }
+
+               public void UpdateDrawable()
+               {
+                       if (BorderElement == null || Control == null)
+                               return;
+
+                       bool cornerRadiusIsDefault = !BorderElement.IsSet(BorderElement.CornerRadiusProperty) || (BorderElement.CornerRadius == (int)BorderElement.CornerRadiusProperty.DefaultValue || BorderElement.CornerRadius == BorderDrawable.DefaultCornerRadius);
+                       bool backgroundColorIsDefault = !BorderElement.IsSet(VisualElement.BackgroundColorProperty) || BorderElement.BackgroundColor == (Color)VisualElement.BackgroundColorProperty.DefaultValue;
+                       bool borderColorIsDefault = !BorderElement.IsSet(BorderElement.BorderColorProperty) || BorderElement.BorderColor == (Color)BorderElement.BorderColorProperty.DefaultValue;
+                       bool borderWidthIsDefault = !BorderElement.IsSet(BorderElement.BorderWidthProperty) || BorderElement.BorderWidth == (double)BorderElement.BorderWidthProperty.DefaultValue;
+
+                       if (backgroundColorIsDefault
+                               && cornerRadiusIsDefault
+                               && borderColorIsDefault
+                               && borderWidthIsDefault)
+                       {
+                               if (!_drawableEnabled)
+                                       return;
+
+                               if (_defaultDrawable != null)
+                                       Control.SetBackground(_defaultDrawable);
+
+                               _drawableEnabled = false;
+                               Reset();
+                       }
+                       else
+                       {
+                               if (_backgroundDrawable == null)
+                                       _backgroundDrawable = new BorderDrawable(Control.Context.ToPixels, Forms.GetColorButtonNormal(Control.Context), _drawOutlineWithBackground);
+
+                               _backgroundDrawable.BorderController = BorderElement;
+
+                               var useDefaultPadding = _renderer.UseDefaultPadding();
+
+                               int paddingTop = useDefaultPadding ? Control.PaddingTop : 0;
+                               int paddingLeft = useDefaultPadding ? Control.PaddingLeft : 0;
+
+                               var useDefaultShadow = _renderer.UseDefaultShadow();
+
+                               // Use no shadow by default for API < 16
+                               float shadowRadius = 0;
+                               float shadowDy = 0;
+                               float shadowDx = 0;
+                               AColor shadowColor = Color.Transparent.ToAndroid();
+                               // Add Android's default material shadow if we want it
+                               if (useDefaultShadow)
+                               {
+                                       shadowRadius = 2;
+                                       shadowDy = 4;
+                                       shadowDx = 0;
+                                       shadowColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
+                               }
+                               // Otherwise get values from the control (but only for supported APIs)
+                               else if ((int)Build.VERSION.SdkInt >= 16)
+                               {
+                                       shadowRadius = _renderer.ShadowRadius;
+                                       shadowDy = _renderer.ShadowDy;
+                                       shadowDx = _renderer.ShadowDx;
+                                       shadowColor = _renderer.ShadowColor;
+                               }
+
+                               _backgroundDrawable.SetPadding(paddingTop, paddingLeft);
+                               if (_renderer.IsShadowEnabled())
+                               {
+                                       _backgroundDrawable
+                                               .SetShadow(shadowDy, shadowDx, shadowColor, shadowRadius);
+                               }
+
+                               if (_drawableEnabled)
+                                       return;
+
+                               if (_defaultDrawable == null)
+                                       _defaultDrawable = Control.Background;
+
+                               if (!backgroundColorIsDefault || _drawOutlineWithBackground)
+                               {
+                                       if (Forms.IsLollipopOrNewer)
+                                       {
+                                               var rippleColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
+
+                                               _rippleDrawable = new RippleDrawable(ColorStateList.ValueOf(rippleColor), _backgroundDrawable, null);
+                                               Control.SetBackground(_rippleDrawable);
+                                       }
+                                       else
+                                       {
+                                               Control.SetBackground(_backgroundDrawable);
+                                       }
+                               }
+
+                               _drawableEnabled = true;
+                       }
+
+                       Control.Invalidate();
+               }
+
+               public void Reset()
+               {
+                       if (_drawableEnabled)
+                       {
+                               _drawableEnabled = false;
+                               _backgroundDrawable?.Reset();
+                               _backgroundDrawable = null;
+                               _rippleDrawable = null;
+                       }
+               }
+
+               public void UpdateBackgroundColor()
+               {
+                       UpdateDrawable();
+               }
+
+               public void Dispose()
+               {
+                       Dispose(true);
+               }
+
+               protected virtual void Dispose(bool disposing)
+               {
+                       if (!_disposed)
+                       {
+                               if (disposing)
+                               {
+                                       _backgroundDrawable?.Dispose();
+                                       _backgroundDrawable = null;
+                                       _defaultDrawable?.Dispose();
+                                       _defaultDrawable = null;
+                                       _rippleDrawable?.Dispose();
+                                       _rippleDrawable = null;
+                                       if (BorderElement != null)
+                                       {
+                                               BorderElement.PropertyChanged -= BorderElementPropertyChanged;
+                                               BorderElement = null;
+                                       }
+
+                                       if (_renderer != null)
+                                       {
+                                               _renderer.ElementChanged -= OnElementChanged;
+                                               _renderer = null;
+                                       }
+                               }
+                               _disposed = true;
+                       }
+               }
+
+               void BorderElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       if (e.PropertyName.Equals(BorderElement.BorderColorProperty.PropertyName) ||
+                               e.PropertyName.Equals(BorderElement.BorderWidthProperty.PropertyName) ||
+                               e.PropertyName.Equals(BorderElement.CornerRadiusProperty.PropertyName) ||
+                               e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.ImageButton.IsShadowEnabledProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.ImageButton.ShadowColorProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.ImageButton.ShadowOffsetProperty.PropertyName) ||
+                               e.PropertyName.Equals(Specifics.ImageButton.ShadowRadiusProperty.PropertyName))
+                       {
+                               Reset();
+                               UpdateDrawable();
+                       }
+               }
+
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/ButtonBackgroundTracker.cs b/Xamarin.Forms.Platform.Android/ButtonBackgroundTracker.cs
deleted file mode 100644 (file)
index d935b43..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-using System;
-using System.ComponentModel;
-using Android.Content.Res;
-using Android.Graphics.Drawables;
-using Android.OS;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using Specifics = Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using AButton = Android.Widget.Button;
-using AColor = Android.Graphics.Color;
-
-namespace Xamarin.Forms.Platform.Android
-{
-       internal class ButtonBackgroundTracker : IDisposable
-       {
-               Drawable _defaultDrawable;
-               ButtonDrawable _backgroundDrawable;
-               RippleDrawable _rippleDrawable;
-               Button _button;
-               AButton _nativeButton;
-               bool _drawableEnabled;
-               bool _disposed;
-
-               public ButtonBackgroundTracker(Button button, AButton nativeButton)
-               {
-                       Button = button;
-                       _nativeButton = nativeButton;
-               }
-
-               public Button Button
-               {
-                       get { return _button; }
-                       set
-                       {
-                               if (_button == value)
-                                       return;
-                               if (_button != null)
-                                       _button.PropertyChanged -= ButtonPropertyChanged;
-                               _button = value;
-                               _button.PropertyChanged += ButtonPropertyChanged;
-                       }
-               }
-
-               public void UpdateDrawable()
-               {
-                       if (_button == null || _nativeButton == null)
-                               return;
-
-                       bool cornerRadiusIsDefault = !_button.IsSet(Button.CornerRadiusProperty) || (_button.CornerRadius == (int)Button.CornerRadiusProperty.DefaultValue || _button.CornerRadius == ButtonDrawable.DefaultCornerRadius);
-                       bool backgroundColorIsDefault = !_button.IsSet(VisualElement.BackgroundColorProperty) || _button.BackgroundColor == (Color)VisualElement.BackgroundColorProperty.DefaultValue;
-                       bool borderColorIsDefault = !_button.IsSet(Button.BorderColorProperty) || _button.BorderColor == (Color)Button.BorderColorProperty.DefaultValue;
-                       bool borderWidthIsDefault = !_button.IsSet(Button.BorderWidthProperty) || _button.BorderWidth == (double)Button.BorderWidthProperty.DefaultValue;
-
-                       if (backgroundColorIsDefault
-                               && cornerRadiusIsDefault
-                               && borderColorIsDefault
-                               && borderWidthIsDefault)
-                       {
-                               if (!_drawableEnabled)
-                                       return;
-
-                               if (_defaultDrawable != null)
-                                       _nativeButton.SetBackground(_defaultDrawable);
-
-                               _drawableEnabled = false;
-                       }
-                       else
-                       {
-                               if (_backgroundDrawable == null)
-                                       _backgroundDrawable = new ButtonDrawable(_nativeButton.Context.ToPixels, Forms.GetColorButtonNormal(_nativeButton.Context));
-
-                               _backgroundDrawable.Button = _button;
-
-                               var useDefaultPadding = _button.OnThisPlatform().UseDefaultPadding();
-
-                               int paddingTop = useDefaultPadding ? _nativeButton.PaddingTop : 0;
-                               int paddingLeft = useDefaultPadding ? _nativeButton.PaddingLeft : 0;
-
-                               var useDefaultShadow = _button.OnThisPlatform().UseDefaultShadow();
-
-                               // Use no shadow by default for API < 16
-                               float shadowRadius = 0;
-                               float shadowDy = 0;
-                               float shadowDx = 0;
-                               AColor shadowColor = Color.Transparent.ToAndroid();
-                               // Add Android's default material shadow if we want it
-                               if (useDefaultShadow)
-                               {
-                                       shadowRadius = 2;
-                                       shadowDy = 4;
-                                       shadowDx = 0;
-                                       shadowColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
-                               }
-                               // Otherwise get values from the control (but only for supported APIs)
-                               else if ((int)Build.VERSION.SdkInt >= 16)
-                               {
-                                       shadowRadius = _nativeButton.ShadowRadius;
-                                       shadowDy = _nativeButton.ShadowDy;
-                                       shadowDx = _nativeButton.ShadowDx;
-                                       shadowColor = _nativeButton.ShadowColor;
-                               }
-
-                               _backgroundDrawable.SetPadding(paddingTop, paddingLeft)
-                                                                  .SetShadow(shadowDy, shadowDx, shadowColor, shadowRadius);
-
-                               if (_drawableEnabled)
-                                       return;
-
-                               if (_defaultDrawable == null)
-                                       _defaultDrawable = _nativeButton.Background;
-
-                               if (Forms.IsLollipopOrNewer)
-                               {
-                                       var rippleColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
-
-                                       _rippleDrawable = new RippleDrawable(ColorStateList.ValueOf(rippleColor), _backgroundDrawable, null);
-                                       _nativeButton.SetBackground(_rippleDrawable);
-                               }
-                               else
-                               {
-                                       _nativeButton.SetBackground(_backgroundDrawable);
-                               }
-
-                               _drawableEnabled = true;
-                       }
-
-                       _nativeButton.Invalidate();
-               }
-
-               public void Reset()
-               {
-                       if (_drawableEnabled)
-                       {
-                               _drawableEnabled = false;
-                               _backgroundDrawable?.Reset();
-                               _backgroundDrawable = null;
-                               _rippleDrawable = null;
-                       }
-               }
-
-               public void Dispose()
-               {
-                       Dispose(true);
-               }
-
-               protected virtual void Dispose(bool disposing)
-               {
-                       if (!_disposed)
-                       {
-                               if (disposing)
-                               {
-                                       _backgroundDrawable?.Dispose();
-                                       _backgroundDrawable = null;
-                                       _defaultDrawable?.Dispose();
-                                       _defaultDrawable = null;
-                                       _rippleDrawable?.Dispose();
-                                       _rippleDrawable = null;
-                                       if (_button != null)
-                                       {
-                                               _button.PropertyChanged -= ButtonPropertyChanged;
-                                               _button = null;
-                                       }
-                                       _nativeButton = null;
-                               }
-                               _disposed = true;
-                       }
-               }
-
-               void ButtonPropertyChanged(object sender, PropertyChangedEventArgs e)
-               {
-                       if (e.PropertyName.Equals(Button.BorderColorProperty.PropertyName) ||
-                               e.PropertyName.Equals(Button.BorderWidthProperty.PropertyName) ||
-                               e.PropertyName.Equals(Button.CornerRadiusProperty.PropertyName) ||
-                               e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName) ||
-                               e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName) ||
-                               e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName))
-                       {
-                               Reset();
-                               UpdateDrawable();
-                       }
-               }
-
-       }
-}
\ No newline at end of file
index d288791..51383fa 100644 (file)
@@ -192,7 +192,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                        try
                        {
-                               await _imageView.UpdateBitmap(null, source, null, previousSource);
+                               await _imageView.UpdateBitmap(source, previousSource).ConfigureAwait(false);
                        }
                        catch (Exception ex)
                        {
index 9be2e26..bf95e5a 100644 (file)
@@ -8,63 +8,62 @@ namespace Xamarin.Forms.Platform.Android
 {
        internal static class ImageViewExtensions
        {
+               public static Task UpdateBitmap(this AImageView imageView, IImageController newView, IImageController previousView) =>
+                       imageView.UpdateBitmap(newView, previousView, null, null);
+
+               public static Task UpdateBitmap(this AImageView imageView, ImageSource newImageSource, ImageSource previousImageSourc) =>
+                       imageView.UpdateBitmap(null, null, newImageSource, previousImageSourc);
+
                // TODO hartez 2017/04/07 09:33:03 Review this again, not sure it's handling the transition from previousImage to 'null' newImage correctly
-               public static async Task UpdateBitmap(this AImageView imageView, Image newImage, ImageSource source, Image previousImage = null, ImageSource previousImageSource = null)
+               static async Task UpdateBitmap(
+                       this AImageView imageView,
+                       IImageController newView,
+                       IImageController previousView,
+                       ImageSource newImageSource,
+                       ImageSource previousImageSource)
                {
-                       if (imageView == null || imageView.IsDisposed())
-                               return;
 
-                       if (Device.IsInvokeRequired)
-                               throw new InvalidOperationException("Image Bitmap must not be updated from background thread");
+                       newImageSource = newView?.Source;
+                       previousImageSource = previousView?.Source;
 
-                       source = source ?? newImage?.Source;
-                       previousImageSource = previousImageSource ?? previousImage?.Source;
-
-                       if (Equals(previousImageSource, source))
+                       if (newImageSource == null || imageView.IsDisposed())
                                return;
 
-                       var imageController = newImage as IImageController;
+                       if (Equals(previousImageSource, newImageSource))
+                               return;
 
-                       imageController?.SetIsLoading(true);
+                       newView?.SetIsLoading(true);
 
                        (imageView as IImageRendererController)?.SkipInvalidate();
-
                        imageView.SetImageResource(global::Android.Resource.Color.Transparent);
 
                        bool setByImageViewHandler = false;
                        Bitmap bitmap = null;
 
-                       if (source != null)
+                       try
                        {
-                               var imageViewHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageViewHandler>(source);
-                               if (imageViewHandler != null)
+                               if (newImageSource != null)
                                {
-                                       try
+                                       var imageViewHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageViewHandler>(newImageSource);
+                                       if (imageViewHandler != null)
                                        {
-                                               await imageViewHandler.LoadImageAsync(source, imageView);
+                                               await imageViewHandler.LoadImageAsync(newImageSource, imageView);
                                                setByImageViewHandler = true;
                                        }
-                                       catch (TaskCanceledException)
-                                       {
-                                               imageController?.SetIsLoading(false);
-                                       }
-                               }
-                               else
-                               {
-                                       var imageSourceHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
-                                       try
-                                       {
-                                               bitmap = await imageSourceHandler.LoadImageAsync(source, imageView.Context);
-                                       }
-                                       catch (TaskCanceledException)
+                                       else
                                        {
-                                               imageController?.SetIsLoading(false);
+                                               var imageSourceHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(newImageSource);
+                                               bitmap = await imageSourceHandler.LoadImageAsync(newImageSource, imageView.Context);
                                        }
                                }
                        }
+                       catch (TaskCanceledException)
+                       {
+                               newView?.SetIsLoading(false);
+                       }
 
                        // Check if the source on the new image has changed since the image was loaded
-                       if (newImage != null && !Equals(newImage.Source, source))
+                       if (!Equals(newView?.Source, newImageSource))
                        {
                                bitmap?.Dispose();
                                return;
@@ -72,24 +71,14 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (!setByImageViewHandler && !imageView.IsDisposed())
                        {
-                               if (bitmap == null && source is FileImageSource)
-                                       imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
+                               if (bitmap == null && newImageSource is FileImageSource)
+                                       imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)newImageSource).File));
                                else
-                               {
                                        imageView.SetImageBitmap(bitmap);
-                               }
                        }
 
                        bitmap?.Dispose();
-                       imageController?.SetIsLoading(false);
-                       ((IVisualElementController)newImage)?.NativeSizeChanged();
-
-               }
-
-               public static async Task UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
-               {
-                       await UpdateBitmap(imageView, newImage, newImage?.Source, previousImage, previousImage?.Source);
-
+                       newView?.SetIsLoading(false);
                }
        }
 }
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ButtonElementManager.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ButtonElementManager.cs
new file mode 100644 (file)
index 0000000..8f5ae0e
--- /dev/null
@@ -0,0 +1,34 @@
+
+using Android.Views;
+using AView = Android.Views.View;
+using AMotionEventActions = Android.Views.MotionEventActions;
+
+
+namespace Xamarin.Forms.Platform.Android.FastRenderers
+{
+       public static class ButtonElementManager
+       {
+
+               public static bool OnTouch(VisualElement element, IButtonController buttonController, AView v, MotionEvent e)
+               {
+                       switch (e.Action)
+                       {
+                               case AMotionEventActions.Down:
+                                       buttonController?.SendPressed();
+                                       break;
+                               case AMotionEventActions.Up:
+                                       buttonController?.SendReleased();
+                                       break;
+                       }
+
+                       return false;
+               }
+
+
+
+               public static void OnClick(VisualElement element, IButtonController buttonController, AView v)
+               {
+                       buttonController?.SendClicked();
+               }
+       }
+}
\ No newline at end of file
index 9d51f0f..9594023 100644 (file)
@@ -8,13 +8,15 @@ using Android.Util;
 using Android.Views;
 using Xamarin.Forms.Internals;
 using AView = Android.Views.View;
-using AMotionEventActions = Android.Views.MotionEventActions;
 using static System.String;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Android.Support.V4.View;
 
 namespace Xamarin.Forms.Platform.Android.FastRenderers
 {
        internal sealed class ButtonRenderer : AppCompatButton, IVisualElementRenderer, AView.IOnAttachStateChangeListener,
-               AView.IOnFocusChangeListener, IEffectControlProvider, AView.IOnClickListener, AView.IOnTouchListener, IViewRenderer, ITabStop
+               AView.IOnFocusChangeListener, AView.IOnClickListener, AView.IOnTouchListener, IViewRenderer, ITabStop, IBorderVisualElementRenderer
        {
                float _defaultFontSize;
                int? _defaultLabelFor;
@@ -24,10 +26,12 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                bool _inputTransparent;
                Lazy<TextColorSwitcher> _textColorSwitcher;
                readonly AutomationPropertiesProvider _automationPropertiesProvider;
-               readonly EffectControlProvider _effectControlProvider;
                VisualElementTracker _tracker;
-               ButtonBackgroundTracker _backgroundTracker;
+               VisualElementRenderer _visualElementRenderer;
+               BorderBackgroundManager _backgroundTracker;
                Thickness _paddingDeltaPix = new Thickness();
+               IPlatformElementConfiguration<PlatformConfiguration.Android, Button> _platformElementConfiguration;
+               private Button _button;
 
                public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
                public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
@@ -35,7 +39,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                public ButtonRenderer(Context context) : base(context)
                {
                        _automationPropertiesProvider = new AutomationPropertiesProvider(this);
-                       _effectControlProvider = new EffectControlProvider(this);
 
                        Initialize();
                }
@@ -44,7 +47,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                public ButtonRenderer() : base(Forms.Context)
                {
                        _automationPropertiesProvider = new AutomationPropertiesProvider(this);
-                       _effectControlProvider = new EffectControlProvider(this);
 
                        Initialize();
                }
@@ -54,35 +56,21 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                ViewGroup IVisualElementRenderer.ViewGroup => null;
                VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
 
-               Button Button { get; set; }
-
-               AView ITabStop.TabStop => this;
-
-               public void OnClick(AView v)
-               {
-                       ((IButtonController)Button)?.SendClicked();
-               }
-
-               public bool OnTouch(AView v, MotionEvent e)
+               Button Button
                {
-                       var buttonController = Element as IButtonController;
-                       switch (e.Action)
+                       get => _button;
+                       set
                        {
-                               case AMotionEventActions.Down:
-                                       buttonController?.SendPressed();
-                                       break;
-                               case AMotionEventActions.Up:
-                                       buttonController?.SendReleased();
-                                       break;
+                               _button = value;
+                               _platformElementConfiguration = null;
                        }
-
-                       return false;
                }
 
-               void IEffectControlProvider.RegisterEffect(Effect effect)
-               {
-                       _effectControlProvider.RegisterEffect(effect);
-               }
+               AView ITabStop.TabStop => this;
+
+               void IOnClickListener.OnClick(AView v) => ButtonElementManager.OnClick(Button, Button, v);
+
+               bool IOnTouchListener.OnTouch(AView v, MotionEvent e) => ButtonElementManager.OnTouch(Button, Button, v, e);
 
                void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView)
                {
@@ -95,8 +83,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
                {
-                       OnNativeFocusChanged(hasFocus);
-
                        ((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
                }
 
@@ -132,16 +118,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                oldElement.PropertyChanged -= OnElementPropertyChanged;
                        }
 
-                       if (_backgroundTracker == null)
-                               _backgroundTracker = new ButtonBackgroundTracker(Button, this);
-                       else
-                               _backgroundTracker.Button = Button;
-
-                       Color currentColor = oldElement?.BackgroundColor ?? Color.Default;
-                       if (element.BackgroundColor != currentColor)
-                       {
-                               UpdateBackgroundColor();
-                       }
 
                        element.PropertyChanged += OnElementPropertyChanged;
 
@@ -150,13 +126,15 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                // Can't set up the tracker in the constructor because it access the Element (for now)
                                SetTracker(new VisualElementTracker(this));
                        }
+                       if (_visualElementRenderer == null)
+                       {
+                               _visualElementRenderer = new VisualElementRenderer(this);
+                       }
 
                        OnElementChanged(new ElementChangedEventArgs<Button>(oldElement as Button, Button));
 
                        SendVisualElementInitialized(element, this);
 
-                       EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
-
                        Performance.Stop(reference);
                }
 
@@ -170,11 +148,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        LabelFor = (int)(id ?? _defaultLabelFor);
                }
 
-               void IVisualElementRenderer.UpdateLayout()
-               {
-                       var reference = Guid.NewGuid().ToString();
-                       _tracker?.UpdateLayout();
-               }
+               void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
 
                void IViewRenderer.MeasureExactly()
                {
@@ -198,7 +172,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                                _automationPropertiesProvider?.Dispose();
                                _tracker?.Dispose();
-
+                               _visualElementRenderer?.Dispose();
                                _backgroundTracker?.Dispose();
                                _backgroundTracker = null;
 
@@ -229,10 +203,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                void OnElementChanged(ElementChangedEventArgs<Button> e)
                {
-                       if (e.OldElement != null)
-                       {
-                               _backgroundTracker?.Reset();
-                       }
                        if (e.NewElement != null && !_isDisposed)
                        {
                                this.EnsureId();
@@ -244,7 +214,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                UpdateText();
                                UpdateBitmap();
                                UpdateTextColor();
-                               UpdateIsEnabled();
                                UpdateInputTransparent();
                                UpdateBackgroundColor();
                                UpdatePadding();
@@ -265,10 +234,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        {
                                UpdateTextColor();
                        }
-                       else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
-                       {
-                               UpdateIsEnabled();
-                       }
                        else if (e.PropertyName == Button.FontProperty.PropertyName)
                        {
                                UpdateFont();
@@ -344,6 +309,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        OnFocusChangeListener = this;
 
                        Tag = this;
+                       _backgroundTracker = new BorderBackgroundManager(this);
                }
 
                void UpdateBitmap()
@@ -439,16 +405,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        }
                }
 
-               void UpdateIsEnabled()
-               {
-                       if (Element == null || _isDisposed)
-                       {
-                               return;
-                       }
-
-                       Enabled = Element.IsEnabled;
-               }
-
                void UpdateInputTransparent()
                {
                        if (Element == null || _isDisposed)
@@ -502,5 +458,21 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        UpdatePadding();
                }
 
+               float IBorderVisualElementRenderer.ShadowRadius => ShadowRadius;
+               float IBorderVisualElementRenderer.ShadowDx => ShadowDx;
+               float IBorderVisualElementRenderer.ShadowDy => ShadowDy;
+               AColor IBorderVisualElementRenderer.ShadowColor => ShadowColor;
+               bool IBorderVisualElementRenderer.UseDefaultPadding() => OnThisPlatform().UseDefaultPadding();
+               bool IBorderVisualElementRenderer.UseDefaultShadow() => OnThisPlatform().UseDefaultShadow();
+               bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+               AView IBorderVisualElementRenderer.View => this;
+
+               IPlatformElementConfiguration<PlatformConfiguration.Android, Button> OnThisPlatform()
+               {
+                       if (_platformElementConfiguration == null)
+                               _platformElementConfiguration = Button.OnThisPlatform();
+
+                       return _platformElementConfiguration;
+               }
        }
 }
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ImageElementManager.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ImageElementManager.cs
new file mode 100644 (file)
index 0000000..fd14b08
--- /dev/null
@@ -0,0 +1,97 @@
+using System.ComponentModel;
+using System.Threading.Tasks;
+using Android.Widget;
+using AScaleType = Android.Widget.ImageView.ScaleType;
+using ARect = Android.Graphics.Rect;
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.Android.FastRenderers
+{
+       public static class ImageElementManager
+       {
+               public static void Init(IVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ElementChanged += OnElementChanged;
+                       renderer.LayoutChange += OnLayoutChange;
+               }
+
+               static void OnLayoutChange(object sender, global::Android.Views.View.LayoutChangeEventArgs e)
+               {
+                       if(sender is IVisualElementRenderer renderer && renderer.View is ImageView imageView)
+                               imageView.ClipBounds = imageView.GetScaleType() == AScaleType.CenterCrop ? new ARect(0, 0, e.Right - e.Left, e.Bottom - e.Top) : null;
+               }
+
+               public static void Dispose(IVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       renderer.ElementChanged -= OnElementChanged;
+                       renderer.LayoutChange -= OnLayoutChange;
+               }
+
+               async static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       var renderer = (sender as IVisualElementRenderer);
+                       var view = renderer.View as ImageView;
+                       var newImageElementManager = e.NewElement as IImageController;
+                       var oldImageElementManager = e.OldElement as IImageController;
+                       var rendererController = renderer as IImageRendererController;
+
+                       await TryUpdateBitmap(rendererController, view, newImageElementManager, oldImageElementManager);
+                       UpdateAspect(rendererController, view, newImageElementManager, oldImageElementManager);
+
+                       if (!rendererController.IsDisposed)
+                       {
+                               ElevationHelper.SetElevation(view, renderer.Element);
+                       }
+               }
+
+               async static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       var renderer = (sender as IVisualElementRenderer);
+                       var ImageElementManager = (IImageController)renderer.Element;
+                       if (e.PropertyName == ImageElementManager.SourceProperty?.PropertyName)
+                       {
+                               try
+                               {
+                                       await TryUpdateBitmap(renderer as IImageRendererController, (ImageView)renderer.View, (IImageController)renderer.Element).ConfigureAwait(false);
+                               }
+                               catch (Exception ex)
+                               {
+                                       Log.Warning(renderer.GetType().Name, "Error loading image: {0}", ex);
+                               }
+                               finally
+                               {
+                                       ImageElementManager?.SetIsLoading(false);
+                               }
+                       }
+                       else if (e.PropertyName == ImageElementManager.AspectProperty?.PropertyName)
+                       {
+                               UpdateAspect(renderer as IImageRendererController, (ImageView)renderer.View, (IImageController)renderer.Element);
+                       }
+               }
+
+
+               async static Task TryUpdateBitmap(IImageRendererController rendererController, ImageView Control, IImageController newImage, IImageController previous = null)
+               {
+                       if (newImage == null || rendererController.IsDisposed)
+                       {
+                               return;
+                       }
+
+                       await Control.UpdateBitmap(newImage, previous).ConfigureAwait(false);
+               }
+
+               static void UpdateAspect(IImageRendererController rendererController, ImageView Control, IImageController newImage, IImageController previous = null)
+               {
+                       if (newImage == null || rendererController.IsDisposed)
+                       {
+                               return;
+                       }
+
+                       ImageView.ScaleType type = newImage.Aspect.ToScaleType();
+                       Control.SetScaleType(type);
+               }
+       }
+}
\ No newline at end of file
index a776fbd..ea22b1f 100644 (file)
@@ -20,6 +20,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                VisualElementRenderer _visualElementRenderer;
                readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
 
+               bool IImageRendererController.IsDisposed => _disposed;
                protected override void Dispose(bool disposing)
                {
                        if (_disposed)
@@ -29,6 +30,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                        if (disposing)
                        {
+                               ImageElementManager.Dispose(this);
+                               BackgroundManager.Dispose(this);
+
                                if (_visualElementTracker != null)
                                {
                                        _visualElementTracker.Dispose();
@@ -64,14 +68,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        base.Invalidate();
                }
 
-               async void OnElementChanged(ElementChangedEventArgs<Image> e)
+               void OnElementChanged(ElementChangedEventArgs<Image> e)
                {
-                       await TryUpdateBitmap(e.OldElement);
-                       UpdateAspect();
                        this.EnsureId();
-
-                       ElevationHelper.SetElevation(this, e.NewElement);
-
                        ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
                }
 
@@ -121,11 +120,15 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        element.PropertyChanged += OnElementPropertyChanged;
 
                        if (_visualElementTracker == null)
+                       {
                                _visualElementTracker = new VisualElementTracker(this);
+                       }
 
                        if (_visualElementRenderer == null)
                        {
                                _visualElementRenderer = new VisualElementRenderer(this);
+                               BackgroundManager.Init(this);
+                               ImageElementManager.Init(this);
                        }
 
                        Performance.Stop(reference);
@@ -134,7 +137,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                        _element?.SendViewInitialized(Control);
                }
-               
+
                void IVisualElementRenderer.SetLabelFor(int? id)
                {
                        if (_defaultLabelFor == null)
@@ -176,61 +179,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                {
                }
 
-               async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
-                       if (e.PropertyName == Image.SourceProperty.PropertyName)
-                               await TryUpdateBitmap();
-                       else if (e.PropertyName == Image.AspectProperty.PropertyName)
-                               UpdateAspect();
-
                        ElementPropertyChanged?.Invoke(this, e);
-               }
-
-               async Task TryUpdateBitmap(Image previous = null)
-               {
-                       // By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
-                       // the application; a custom renderer can override this method and handle exceptions from
-                       // UpdateBitmap differently if it wants to
-
-                       try
-                       {
-                               await UpdateBitmap(previous);
-                       }
-                       catch (Exception ex)
-                       {
-                               Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
-                       }
-                       finally
-                       {
-                               ((IImageController)_element)?.SetIsLoading(false);
-                       }
-               }
-
-               async Task UpdateBitmap(Image previous = null)
-               {
-                       if (_element == null || _disposed)
-                       {
-                               return;
-                       }
-
-                       await Control.UpdateBitmap(_element, previous);
-               }
-
-               protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
-               {
-                       base.OnLayout(changed, left, top, right, bottom);
-                       ClipBounds = GetScaleType() == ScaleType.CenterCrop ? new Rect(0, 0, right - left, bottom - top) : null;
-               }
-
-               void UpdateAspect()
-               {
-                       if (_element == null || _disposed)
-                       {
-                               return;
-                       }
-
-                       ScaleType type = _element.Aspect.ToScaleType();
-                       SetScaleType(type);
-               }
-       }
+        }
+    }
 }
index 3bad84b..ca4e717 100644 (file)
@@ -35,13 +35,15 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                {
                        _labelTextColorDefault = TextColors;
                        _visualElementRenderer = new VisualElementRenderer(this);
+                       BackgroundManager.Init(this);
                }
 
                [Obsolete("This constructor is obsolete as of version 2.5. Please use LabelRenderer(Context) instead.")]
-               public LabelRenderer(): base(Forms.Context)
+               public LabelRenderer() : base(Forms.Context)
                {
                        _labelTextColorDefault = TextColors;
                        _visualElementRenderer = new VisualElementRenderer(this);
+                       BackgroundManager.Init(this);
                }
 
                public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
@@ -70,17 +72,17 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                                OnElementChanged(new ElementChangedEventArgs<Label>(oldElement, _element));
 
-                           _element?.SendViewInitialized(this);
+                               _element?.SendViewInitialized(this);
                        }
                }
 
                SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
                {
-                       if (_disposed)
-                       {
-                               return new SizeRequest();
-                       }
-               
+                       if (_disposed)
+                       {
+                               return new SizeRequest();
+                       }
+
                        if (_lastSizeRequest.HasValue)
                        {
                                // if we are measuring the same thing, no need to waste the time
@@ -163,6 +165,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                        if (disposing)
                        {
+                               BackgroundManager.Dispose(this);
                                if (_visualElementTracker != null)
                                {
                                        _visualElementTracker.Dispose();
@@ -187,17 +190,17 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        base.Dispose(disposing);
                }
 
-        public override bool OnTouchEvent(MotionEvent e)
-        {
-               if (_visualElementRenderer.OnTouchEvent(e) || base.OnTouchEvent(e))
-               {
-                       return true;
-               }
+               public override bool OnTouchEvent(MotionEvent e)
+               {
+                       if (_visualElementRenderer.OnTouchEvent(e) || base.OnTouchEvent(e))
+                       {
+                               return true;
+                       }
 
-               return _motionEventHelper.HandleMotionEvent(Parent, e);
-        }
+                       return _motionEventHelper.HandleMotionEvent(Parent, e);
+               }
 
-        void OnElementChanged(ElementChangedEventArgs<Label> e)
+               void OnElementChanged(ElementChangedEventArgs<Label> e)
                {
                        ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
 
index ec4518a..2084090 100644 (file)
@@ -10,7 +10,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
        internal sealed class VisualElementRenderer : IDisposable, IEffectControlProvider, ITabStop
        {
                bool _disposed;
-               
+
                IVisualElementRenderer _renderer;
                readonly GestureManager _gestureManager;
                readonly AutomationPropertiesProvider _automationPropertiesProvider;
@@ -38,13 +38,6 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        _effectControlProvider.RegisterEffect(effect);
                }
 
-               public void UpdateBackgroundColor(Color? color = null)
-               {               
-                       if (_disposed || Element == null || Control == null)
-                               return;
-
-                       Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid());
-               }
 
                void UpdateFlowDirection()
                {
@@ -55,11 +48,11 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                }
 
                public bool OnTouchEvent(MotionEvent e)
-           {
-               return _gestureManager.OnTouchEvent(e);
-           }
+               {
+                       return _gestureManager.OnTouchEvent(e);
+               }
 
-           public void Dispose()
+               public void Dispose()
                {
                        Dispose(true);
                        GC.SuppressFinalize(this);
@@ -88,6 +81,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
 
                void OnElementChanged(object sender, VisualElementChangedEventArgs e)
                {
+                       Performance.Start(out string reference);
                        if (e.OldElement != null)
                        {
                                e.OldElement.PropertyChanged -= OnElementPropertyChanged;
@@ -96,19 +90,34 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                        if (e.NewElement != null)
                        {
                                e.NewElement.PropertyChanged += OnElementPropertyChanged;
-                               UpdateBackgroundColor();
                                UpdateFlowDirection();
+                               UpdateIsEnabled();
                        }
 
                        EffectUtilities.RegisterEffectControlProvider(this, e.OldElement, e.NewElement);
+                       Performance.Stop(reference);
+               }
+
+               void UpdateIsEnabled()
+               {
+                       if (Element == null || _disposed)
+                       {
+                               return;
+                       }
+
+                       Control.Enabled = Element.IsEnabled;
                }
 
                void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
-                       if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
-                               UpdateBackgroundColor();
-                       else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
+                       if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
+                       {
                                UpdateFlowDirection();
+                       }
+                       else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+                       {
+                               UpdateIsEnabled();
+                       }
                }
        }
 }
diff --git a/Xamarin.Forms.Platform.Android/IBorderVisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/IBorderVisualElementRenderer.cs
new file mode 100644 (file)
index 0000000..3c3177d
--- /dev/null
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Android.App;
+using Android.Content;
+using Android.OS;
+using Android.Runtime;
+using Android.Views;
+using Android.Widget;
+using AColor = Android.Graphics.Color;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       public interface IBorderVisualElementRenderer
+       {
+               float ShadowRadius { get; }
+               float ShadowDx { get; }
+               float ShadowDy { get; }
+               AColor ShadowColor { get; }
+               bool UseDefaultPadding();
+               bool UseDefaultShadow();
+               bool IsShadowEnabled();
+               VisualElement Element { get; }
+               AView View { get; }
+               event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+       }
+}
\ No newline at end of file
index 86f46c5..195f0ef 100644 (file)
@@ -2,6 +2,7 @@ using System;
 using System.ComponentModel;
 using Android.Views;
 using AView = Android.Views.View;
+using ALayoutChangeEventArgs = Android.Views.View.LayoutChangeEventArgs;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -27,5 +28,7 @@ namespace Xamarin.Forms.Platform.Android
                void SetLabelFor(int? id);
 
                void UpdateLayout();
+
+               event EventHandler<ALayoutChangeEventArgs> LayoutChange;
        }
 }
\ No newline at end of file
index 5e89bab..877017b 100644 (file)
@@ -29,6 +29,7 @@ using Xamarin.Forms.Platform.Android;
 [assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
 [assembly: ExportRenderer (typeof (Image), typeof (ImageRenderer))]
 [assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))]
+[assembly: ExportRenderer (typeof (ImageButton), typeof (ImageButtonRenderer))]
 [assembly: ExportRenderer (typeof (TableView), typeof (TableViewRenderer))]
 [assembly: ExportRenderer (typeof (ListView), typeof (ListViewRenderer))]
 [assembly: ExportRenderer (typeof (Slider), typeof (SliderRenderer))]
@@ -6,7 +6,7 @@ using AColor = Android.Graphics.Color;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       internal class ButtonDrawable : Drawable
+       internal class BorderDrawable : Drawable
        {
                public const int DefaultCornerRadius = 2; // Default value for Android material button.
 
@@ -18,7 +18,7 @@ namespace Xamarin.Forms.Platform.Android
                float _paddingLeft;
                float _paddingTop;
                Color _defaultColor;
-
+               readonly bool _drawOutlineWithBackground;
                AColor _shadowColor;
                float _shadowDx;
                float _shadowDy;
@@ -36,14 +36,19 @@ namespace Xamarin.Forms.Platform.Android
                        set { _paddingTop = value; }
                }
 
-               public ButtonDrawable(Func<double, float> convertToPixels, Color defaultColor)
+               public BorderDrawable(Func<double, float> convertToPixels, Color defaultColor, bool drawOutlineWithBackground)
                {
                        _convertToPixels = convertToPixels;
                        _pressed = false;
                        _defaultColor = defaultColor;
+                       _drawOutlineWithBackground = drawOutlineWithBackground;
                }
 
-               public Button Button { get; set; }
+               public IBorderController BorderController
+               {
+                       get;
+                       set;
+               }
 
                public override bool IsStateful
                {
@@ -57,6 +62,7 @@ namespace Xamarin.Forms.Platform.Android
 
                public override void Draw(Canvas canvas)
                {
+                       //Bounds = new Rect(Bounds.Left, Bounds.Top, Bounds.Right + (int)_convertToPixels(10), Bounds.Bottom + (int)_convertToPixels(10));
                        int width = Bounds.Width();
                        int height = Bounds.Height();
 
@@ -70,6 +76,9 @@ namespace Xamarin.Forms.Platform.Android
                                _normalBitmap.Width != width)
                                Reset();
 
+                       if (!_drawOutlineWithBackground && BorderController.BackgroundColor == Color.Default)
+                               return;
+
                        Bitmap bitmap = null;
                        if (GetState().Contains(global::Android.Resource.Attribute.StatePressed))
                        {
@@ -85,7 +94,7 @@ namespace Xamarin.Forms.Platform.Android
                        canvas.DrawBitmap(bitmap, 0, 0, new Paint());
                }
 
-               public ButtonDrawable SetShadow(float dy, float dx, AColor color, float radius)
+               public BorderDrawable SetShadow(float dy, float dx, AColor color, float radius)
                {
                        _shadowDx = dx;
                        _shadowDy = dy;
@@ -94,7 +103,7 @@ namespace Xamarin.Forms.Platform.Android
                        return this;
                }
 
-               public ButtonDrawable SetPadding(float top, float left)
+               public BorderDrawable SetPadding(float top, float left)
                {
                        _paddingTop = top;
                        _paddingLeft = left;
@@ -132,7 +141,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                }
 
-               public Color BackgroundColor => Button.BackgroundColor == Color.Default ? _defaultColor : Button.BackgroundColor;
+               public Color BackgroundColor => BorderController.BackgroundColor == Color.Default ? _defaultColor : BorderController.BackgroundColor;
                public Color PressedBackgroundColor => BackgroundColor.AddLuminosity(-.12);//<item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
 
                protected override void Dispose(bool disposing)
@@ -166,7 +175,8 @@ namespace Xamarin.Forms.Platform.Android
                        using (var canvas = new Canvas(bitmap))
                        {
                                DrawBackground(canvas, width, height, pressed);
-                               DrawOutline(canvas, width, height);
+                               if (_drawOutlineWithBackground)
+                                       DrawOutline(canvas, width, height);
                        }
 
                        return bitmap;
@@ -196,21 +206,28 @@ namespace Xamarin.Forms.Platform.Android
                {
                        int cornerRadius = DefaultCornerRadius;
 
-                       if (Button.IsSet(Button.CornerRadiusProperty) && Button.CornerRadius != (int)Button.CornerRadiusProperty.DefaultValue)
-                               cornerRadius = Button.CornerRadius;
+                       if (BorderController.IsSet(BorderController.CornerRadiusProperty) && BorderController.CornerRadius != (int)BorderController.CornerRadiusProperty.DefaultValue)
+                               cornerRadius = BorderController.CornerRadius;
 
                        return _convertToPixels(cornerRadius);
                }
 
-               void DrawOutline(Canvas canvas, int width, int height)
+               public RectF GetPaddingBounds(int width, int height)
+               {
+                       RectF rect = new RectF(0, 0, width, height);
+                       rect.Inset(PaddingLeft, PaddingTop);
+                       return rect;
+               }
+
+               public void DrawOutline(Canvas canvas, int width, int height)
                {
-                       if (Button.BorderWidth <= 0)
+                       if (BorderController.BorderWidth <= 0)
                                return;
 
                        using (var paint = new Paint { AntiAlias = true })
                        using (var path = new Path())
                        {
-                               float borderWidth = _convertToPixels(Button.BorderWidth);
+                               float borderWidth = _convertToPixels(BorderController.BorderWidth);
                                float inset = borderWidth / 2;
 
                                // adjust border radius so outer edge of stroke is same radius as border radius of background
@@ -222,7 +239,7 @@ namespace Xamarin.Forms.Platform.Android
                                path.AddRoundRect(rect, borderRadius, borderRadius, Path.Direction.Cw);
                                paint.StrokeWidth = borderWidth;
                                paint.SetStyle(Paint.Style.Stroke);
-                               paint.Color = Button.BorderColor.ToAndroid();
+                               paint.Color = BorderController.BorderColor.ToAndroid();
 
                                canvas.DrawPath(path, paint);
                        }
index 6635904..34ed43e 100644 (file)
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.ComponentModel;
 using Android.Content;
 using Android.Graphics;
@@ -10,28 +10,34 @@ using AView = Android.Views.View;
 using AMotionEvent = Android.Views.MotionEvent;
 using AMotionEventActions = Android.Views.MotionEventActions;
 using Object = Java.Lang.Object;
+using AColor = Android.Graphics.Color;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       public class ButtonRenderer : ViewRenderer<Button, AButton>, AView.IOnAttachStateChangeListener
+       public class ButtonRenderer : ViewRenderer<Button, AButton>, AView.IOnAttachStateChangeListener, IBorderVisualElementRenderer
        {
-               ButtonBackgroundTracker _backgroundTracker;
+               BorderBackgroundManager _backgroundTracker;
                TextColorSwitcher _textColorSwitcher;
                float _defaultFontSize;
                Typeface _defaultTypeface;
                bool _isDisposed;
                int _imageHeight = -1;
                Thickness _paddingDeltaPix = new Thickness();
+               IVisualElementRenderer _visualElementRenderer;
 
                public ButtonRenderer(Context context) : base(context)
                {
                        AutoPackage = false;
+                       _visualElementRenderer = this;
+                       _backgroundTracker = new BorderBackgroundManager(this);
                }
 
                [Obsolete("This constructor is obsolete as of version 2.5. Please use ButtonRenderer(Context) instead.")]
                public ButtonRenderer()
                {
                        AutoPackage = false;
+                       _visualElementRenderer = this;
+                       _backgroundTracker = new BorderBackgroundManager(this);
                }
 
                AButton NativeButton
@@ -83,6 +89,7 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                _backgroundTracker?.Dispose();
                                _backgroundTracker = null;
+                               _visualElementRenderer = null;
                        }
 
                        base.Dispose(disposing);
@@ -115,11 +122,6 @@ namespace Xamarin.Forms.Platform.Android
                                }
                        }
 
-                       if (_backgroundTracker == null)
-                               _backgroundTracker = new ButtonBackgroundTracker(Element, Control);
-                       else
-                               _backgroundTracker.Button = e.NewElement;
-
                        UpdateAll();
                }
 
@@ -264,6 +266,21 @@ namespace Xamarin.Forms.Platform.Android
                        _textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
                }
 
+               float IBorderVisualElementRenderer.ShadowRadius => Control.ShadowRadius;
+               float IBorderVisualElementRenderer.ShadowDx => Control.ShadowDx;
+               float IBorderVisualElementRenderer.ShadowDy => Control.ShadowDy;
+               AColor IBorderVisualElementRenderer.ShadowColor => Control.ShadowColor;
+               bool IBorderVisualElementRenderer.UseDefaultPadding() => Element.OnThisPlatform().UseDefaultPadding();
+               bool IBorderVisualElementRenderer.UseDefaultShadow() => Element.OnThisPlatform().UseDefaultShadow();
+               bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+               VisualElement IBorderVisualElementRenderer.Element => Element;
+               AView IBorderVisualElementRenderer.View => Control;
+               event EventHandler<VisualElementChangedEventArgs> IBorderVisualElementRenderer.ElementChanged
+               {
+                       add => _visualElementRenderer.ElementChanged += value;
+                       remove => _visualElementRenderer.ElementChanged -= value;
+               }
+
                void UpdatePadding()
                {
                        Control?.SetPadding(
index dc0bd2f..d14fa85 100644 (file)
@@ -32,5 +32,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                        _skipInvalidate = true;
                }
+
+               bool IImageRendererController.IsDisposed => false;
        }
 }
\ No newline at end of file
index 407cf2a..d04b9d3 100644 (file)
@@ -12,6 +12,7 @@ namespace Xamarin.Forms.Platform.Android
        internal interface IImageRendererController
        {
                void SkipInvalidate();
+               bool IsDisposed { get; }
        }
 
        public class ImageRenderer : ViewRenderer<Image, AImageView>
@@ -110,7 +111,7 @@ namespace Xamarin.Forms.Platform.Android
                                return;
                        }
 
-                       await Control.UpdateBitmap(Element, previous);
+                       await Control.UpdateBitmap(Element, previous).ConfigureAwait(false);
                }
 
                public override bool OnTouchEvent(MotionEvent e)
index 99a471e..f0c5428 100644 (file)
@@ -7,6 +7,7 @@ using Android.Views;
 using Android.Widget;
 using AView = Android.Views.View;
 using Xamarin.Forms.Internals;
+using AImageButton = Android.Widget.ImageButton;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -78,14 +79,14 @@ namespace Xamarin.Forms.Platform.Android
 
                class MenuElementView : LinearLayout
                {
-                       readonly ImageButton _image;
+                       readonly AImageButton _image;
                        readonly TextView _label;
                        string _icon;
 
                        public MenuElementView(Context context) : base(context)
                        {
                                Orientation = Orientation.Vertical;
-                               _image = new ImageButton(context);
+                               _image = new AImageButton(context);
                                _image.SetScaleType(ImageView.ScaleType.FitCenter);
                                _image.Click += (object sender, EventArgs e) =>
                                {
index eb8ac79..7c91457 100644 (file)
@@ -81,7 +81,9 @@ namespace Xamarin.Forms.Platform.Android
                                        _defaultTrackDrawable = Control.TrackDrawable;
                                }
                                else
+                               {
                                        UpdateEnabled(); // Normally set by SetNativeControl, but not when the Control is reused.
+                               }
 
                                e.NewElement.Toggled += HandleToggled;
                                Control.Checked = e.NewElement.IsToggled;
index b43d36d..3b333b4 100644 (file)
@@ -2,11 +2,11 @@ using System;
 using System.ComponentModel;
 using Android.Content;
 using Android.Graphics;
-using Android.Widget;
+using AImageButton = Android.Widget.ImageButton;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       internal sealed class ToolbarImageButton : ImageButton, IToolbarButton
+       internal sealed class ToolbarImageButton : AImageButton, IToolbarButton
        {
                IMenuItemController Controller => Item;
                public ToolbarImageButton(Context context, ToolbarItem item) : base(context)
index c1411c5..a956d23 100644 (file)
@@ -182,8 +182,7 @@ namespace Xamarin.Forms.Platform.Android
                        TElement oldElement = Element;
                        Element = element;
 
-                       var reference = Guid.NewGuid().ToString();
-                       Performance.Start(reference);
+                       Performance.Start(out string reference);
 
                        if (oldElement != null)
                        {
index 923b7c5..c0254ba 100644 (file)
@@ -74,8 +74,7 @@ namespace Xamarin.Forms.Platform.Android
 
                public void UpdateLayout()
                {
-                       var reference = Guid.NewGuid().ToString();
-                       Performance.Start(reference);
+                       Performance.Start(out string reference);
 
                        VisualElement view = _renderer.Element;
                        AView aview = _renderer.View;
@@ -154,7 +153,7 @@ namespace Xamarin.Forms.Platform.Android
                                UpdateAnchorX();
                        else if (e.PropertyName == VisualElement.AnchorYProperty.PropertyName)
                                UpdateAnchorY();
-                       else if (   e.PropertyName == VisualElement.ScaleProperty.PropertyName
+                       else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName
                                         || e.PropertyName == VisualElement.ScaleXProperty.PropertyName
                                         || e.PropertyName == VisualElement.ScaleYProperty.PropertyName)
                                UpdateScale();
index 1180150..ddda6eb 100644 (file)
     <Compile Include="AndroidApplicationLifecycleState.cs" />
     <Compile Include="AndroidTitleBarVisibility.cs" />
     <Compile Include="AppCompat\FrameRenderer.cs" />
+    <Compile Include="BackgroundManager.cs" />
     <Compile Include="AppCompat\ILifeCycleState.cs" />
-    <Compile Include="ButtonBackgroundTracker.cs" />
+    <Compile Include="BorderBackgroundManager.cs" />
     <Compile Include="Elevation.cs" />
     <Compile Include="Extensions\EntryRendererExtensions.cs" />
     <Compile Include="Extensions\FragmentManagerExtensions.cs" />
     <Compile Include="FastRenderers\AutomationPropertiesProvider.cs" />
     <Compile Include="AppCompat\PageExtensions.cs" />
     <Compile Include="Extensions\JavaObjectExtensions.cs" />
+    <Compile Include="FastRenderers\ButtonElementManager.cs" />
     <Compile Include="FastRenderers\ButtonRenderer.cs" />
     <Compile Include="AppCompat\FormsViewPager.cs" />
     <Compile Include="AppCompat\FragmentContainer.cs" />
     <Compile Include="ExportImageSourceHandlerAttribute.cs" />
     <Compile Include="ExportRendererAttribute.cs" />
     <Compile Include="Extensions\FlowDirectionExtensions.cs" />
+    <Compile Include="AppCompat\ImageButtonRenderer.cs" />
+    <Compile Include="FastRenderers\ImageElementManager.cs" />
     <Compile Include="GestureManager.cs" />
     <Compile Include="FastRenderers\LabelRenderer.cs" />
     <Compile Include="FastRenderers\VisualElementRenderer.cs" />
     <Compile Include="ColorExtensions.cs" />
     <Compile Include="ContextExtensions.cs" />
     <Compile Include="GetDesiredSizeDelegate.cs" />
+    <Compile Include="IBorderVisualElementRenderer.cs" />
     <Compile Include="IDeviceInfoProvider.cs" />
     <Compile Include="ITabStop.cs" />
     <Compile Include="Renderers\FormsWebViewClient.cs" />
     <Compile Include="LayoutExtensions.cs" />
     <Compile Include="PowerSaveModeBroadcastReceiver.cs" />
     <Compile Include="Renderers\AHorizontalScrollView.cs" />
-    <Compile Include="Renderers\ButtonDrawable.cs" />
     <Compile Include="Renderers\AlignmentExtensions.cs" />
+    <Compile Include="Renderers\BorderDrawable.cs" />
+    <Compile Include="Renderers\ButtonRenderer.cs" />
     <Compile Include="Renderers\ConditionalFocusLayout.cs" />
     <Compile Include="Renderers\DescendantFocusToggler.cs" />
     <Compile Include="Renderers\FormsEditText.cs" />
     <Compile Include="Renderers\ActionSheetRenderer.cs" />
     <Compile Include="Renderers\ActivityIndicatorRenderer.cs" />
     <Compile Include="Renderers\BoxRenderer.cs" />
-    <Compile Include="Renderers\ButtonRenderer.cs" />
     <Compile Include="Renderers\CarouselPageRenderer.cs" />
     <Compile Include="Renderers\DatePickerRenderer.cs" />
     <Compile Include="Renderers\EditorRenderer.cs" />
index a110a02..506a4ea 100644 (file)
@@ -3,10 +3,11 @@ using System;
 using System.ComponentModel;
 using Xamarin.Forms.Platform.GTK.Controls;
 using Xamarin.Forms.Platform.GTK.Extensions;
+using GtkImageButton = Xamarin.Forms.Platform.GTK.Controls.ImageButton;
 
 namespace Xamarin.Forms.Platform.GTK.Renderers
 {
-       public class ButtonRenderer : ViewRenderer<Button, ImageButton>
+       public class ButtonRenderer : ViewRenderer<Button, GtkImageButton>
        {
                private const uint DefaultBorderWidth = 1;
 
@@ -30,9 +31,9 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
                        {
                                if (Control == null)
                                {
-                                       // To allow all avalaible options in Xamarin.Forms, a custom control has been created.
+                                       // To allow all available options in Xamarin.Forms, a custom control has been created.
                                        // Can set text, text color, border, image, etc.
-                                       var btn = new ImageButton();
+                                       var btn = new GtkImageButton();
                                        SetNativeControl(btn);
 
                                        Control.Clicked += OnButtonClicked;
@@ -93,7 +94,7 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
                {
                        var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
 
-                       if (string.IsNullOrWhiteSpace(elemValue) 
+                       if (string.IsNullOrWhiteSpace(elemValue)
                                && Control?.Accessible.Description == Control?.LabelWidget.Text)
                                return;
 
index 008a6f1..3046aa8 100644 (file)
@@ -9,7 +9,7 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
 {
        public class ImageRenderer : ViewRenderer<Image, Controls.ImageControl>
        {
-               private bool _isDisposed;
+               bool _isDisposed;
 
                protected override void Dispose(bool disposing)
                {
@@ -67,7 +67,7 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
                        Control.SetSizeRequest(allocation.Width, allocation.Height);
                }
 
-               private async void SetImage(Image oldElement = null)
+               async void SetImage(Image oldElement = null)
                {
                        var source = Element.Source;
 
@@ -122,7 +122,7 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
                                ((IImageController)Element).SetIsLoading(false);
                }
 
-               private void SetAspect()
+               void SetAspect()
                {
                        switch (Element.Aspect)
                        {
@@ -140,7 +140,7 @@ namespace Xamarin.Forms.Platform.GTK.Renderers
                        }
                }
 
-               private void SetOpacity()
+               void SetOpacity()
                {
                        var opacity = Element.Opacity;
 
index 8530117..8d53b7e 100644 (file)
@@ -75,6 +75,9 @@
       <Link>Flags.cs</Link>
     </Compile>
     <Compile Include="Controls\FormsBoxView.cs" />
+    <Compile Include="..\Xamarin.Forms.Platform.iOS\IVisualNativeElementRenderer.cs">
+      <Link>IVisualNativeElementRenderer.cs</Link>
+    </Compile>
     <Compile Include="Controls\FormsNSDatePicker.cs" />
     <Compile Include="Extensions\FlowDirectionExtensions.cs" />
     <Compile Include="FormsApplicationDelegate.cs" />
diff --git a/Xamarin.Forms.Platform.UAP/IImageVisualElementRenderer.cs b/Xamarin.Forms.Platform.UAP/IImageVisualElementRenderer.cs
new file mode 100644 (file)
index 0000000..25e7114
--- /dev/null
@@ -0,0 +1,10 @@
+
+namespace Xamarin.Forms.Platform.UWP
+{
+       public interface IImageVisualElementRenderer : IVisualNativeElementRenderer
+       {
+               bool IsDisposed { get; }
+               void SetImage(Windows.UI.Xaml.Media.ImageSource image);
+               Windows.UI.Xaml.Controls.Image GetImage();
+       }
+}
diff --git a/Xamarin.Forms.Platform.UAP/IVisualNativeElementRenderer.cs b/Xamarin.Forms.Platform.UAP/IVisualNativeElementRenderer.cs
new file mode 100644 (file)
index 0000000..d4c8b6c
--- /dev/null
@@ -0,0 +1,12 @@
+using System;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+       public interface IVisualNativeElementRenderer : IVisualElementRenderer
+       {
+               event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+               event EventHandler ControlChanging;
+               event EventHandler ControlChanged;
+       }
+}
diff --git a/Xamarin.Forms.Platform.UAP/ImageButtonRenderer.cs b/Xamarin.Forms.Platform.UAP/ImageButtonRenderer.cs
new file mode 100644 (file)
index 0000000..18e2cc6
--- /dev/null
@@ -0,0 +1,270 @@
+using System;
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+using Xamarin.Forms.Internals;
+using WThickness = Windows.UI.Xaml.Thickness;
+using WImage = Windows.UI.Xaml.Controls.Image;
+using Windows.UI.Xaml.Input;
+using System.Threading.Tasks;
+using System.Diagnostics;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+       public class ImageButtonRenderer : ViewRenderer<ImageButton, FormsButton>, IImageVisualElementRenderer
+       {
+               bool _measured;
+               bool _disposed;
+               WImage _image;
+               FormsButton _formsButton;
+
+               public ImageButtonRenderer() : base()
+               {
+                       ImageElementManager.Init(this);
+               }
+
+
+               protected override void Dispose(bool disposing)
+               {
+                       if (_disposed)
+                       {
+                               return;
+                       }
+
+                       _disposed = true;
+
+                       if (disposing)
+                       {
+                               ImageElementManager.Dispose(this);
+                               if (Control != null)
+                               {
+                                       _image.ImageOpened -= OnImageOpened;
+                                       _image.ImageFailed -= OnImageFailed;
+                               }
+                       }
+
+                       base.Dispose(disposing);
+               }
+
+               public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+               {
+                       if (_image?.Source == null)
+                               return new SizeRequest();
+
+                       _measured = true;
+
+
+                       var result = new Size
+                       {
+                               Width = ((BitmapSource)_image.Source).PixelWidth,
+                               Height = ((BitmapSource)_image.Source).PixelHeight
+                       };
+
+                       return new SizeRequest(result);
+               }
+
+
+               protected async override void OnElementChanged(ElementChangedEventArgs<ImageButton> e)
+               {
+                       base.OnElementChanged(e);
+
+                       if (e.NewElement != null)
+                       {
+                               if (Control == null)
+                               {
+                                       _formsButton = new FormsButton();
+                                       _formsButton.Padding = new WThickness(0);
+                                       _formsButton.BorderThickness = new WThickness(0);
+                                       _formsButton.Background = null;
+
+                                       _image = new Windows.UI.Xaml.Controls.Image()
+                                       {
+                                               VerticalAlignment = VerticalAlignment.Center,
+                                               HorizontalAlignment = HorizontalAlignment.Center,
+                                               Stretch = Stretch.Uniform,
+                                       };
+
+                                       _image.ImageOpened += OnImageOpened;
+                                       _image.ImageFailed += OnImageFailed;
+                                       _formsButton.Content = _image;
+
+                                       _formsButton.Click += OnButtonClick;
+                                       _formsButton.AddHandler(PointerPressedEvent, new PointerEventHandler(OnPointerPressed), true);
+                                       _formsButton.Loaded += ButtonOnLoaded;
+
+                                       SetNativeControl(_formsButton);
+
+                               }
+                               else
+                               {
+                                       WireUpFormsVsm();
+                               }
+
+                               //TODO: We may want to revisit this strategy later. If a user wants to reset any of these to the default, the UI won't update.
+                               if (Element.IsSet(VisualElement.BackgroundColorProperty) && Element.BackgroundColor != (Color)VisualElement.BackgroundColorProperty.DefaultValue)
+                                       UpdateBackground();
+
+                               if (Element.IsSet(ImageButton.BorderColorProperty) && Element.BorderColor != (Color)ImageButton.BorderColorProperty.DefaultValue)
+                                       UpdateBorderColor();
+
+                               if (Element.IsSet(ImageButton.BorderWidthProperty) && Element.BorderWidth != (double)ImageButton.BorderWidthProperty.DefaultValue)
+                                       UpdateBorderWidth();
+
+                               if (Element.IsSet(ImageButton.CornerRadiusProperty) && Element.CornerRadius != (int)ImageButton.CornerRadiusProperty.DefaultValue)
+                                       UpdateBorderRadius();
+                               if (Element.IsSet(Button.PaddingProperty) && Element.Padding != (Thickness)Button.PaddingProperty.DefaultValue)
+                                       UpdatePadding();
+
+                               await TryUpdateSource().ConfigureAwait(false);
+
+                       }
+               }
+
+               protected virtual async Task TryUpdateSource()
+               {
+                       // By default we'll just catch and log any exceptions thrown by UpdateSource so we don't bring down
+                       // the application; a custom renderer can override this method and handle exceptions from
+                       // UpdateSource differently if it wants to
+
+                       try
+                       {
+                               await UpdateSource().ConfigureAwait(false);
+                       }
+                       catch (Exception ex)
+                       {
+                               Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+                       }
+                       finally
+                       {
+                               ((IImageController)Element)?.SetIsLoading(false);
+                       }
+               }
+
+               protected async Task UpdateSource()
+               {
+                       await ImageElementManager.UpdateSource(this).ConfigureAwait(false);
+               }
+
+
+               void OnImageOpened(object sender, RoutedEventArgs routedEventArgs)
+               {
+                       if (_measured)
+                       {
+                               ImageElementManager.RefreshImage(Element);
+                       }
+
+                       Element?.SetIsLoading(false);
+               }
+
+               protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
+               {
+                       Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}");
+                       Element?.SetIsLoading(false);
+               }
+
+
+
+               void ButtonOnLoaded(object o, RoutedEventArgs routedEventArgs)
+               {
+                       WireUpFormsVsm();
+               }
+
+               void WireUpFormsVsm()
+               {
+                       if (Element.UseFormsVsm())
+                       {
+                               InterceptVisualStateManager.Hook(Control.GetFirstDescendant<Windows.UI.Xaml.Controls.Grid>(), Control, Element);
+                       }
+               }
+
+               protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       base.OnElementPropertyChanged(sender, e);
+
+                       if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+                       {
+                               UpdateBackground();
+                       }
+                       else if (e.PropertyName == ImageButton.BorderColorProperty.PropertyName)
+                       {
+                               UpdateBorderColor();
+                       }
+                       else if (e.PropertyName == ImageButton.BorderWidthProperty.PropertyName)
+                       {
+                               UpdateBorderWidth();
+                       }
+                       else if (e.PropertyName == ImageButton.CornerRadiusProperty.PropertyName)
+                       {
+                               UpdateBorderRadius();
+                       }
+                       else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+                       {
+                               UpdatePadding();
+                       }
+                       else if (e.PropertyName == ImageButton.SourceProperty.PropertyName)
+                               await TryUpdateSource().ConfigureAwait(false);
+               }
+               void UpdatePadding()
+               {
+                       _image.Margin = new WThickness(0);
+
+                       Control.Padding = new WThickness(
+                                       Element.Padding.Left,
+                                       Element.Padding.Top,
+                                       Element.Padding.Right,
+                                       Element.Padding.Bottom
+                               );
+               }
+               protected override void UpdateBackgroundColor()
+               {
+                       // Button is a special case; we don't want to set the Control's background
+                       // because it goes outside the bounds of the Border/ContentPresenter, 
+                       // which is where we might change the BorderRadius to create a rounded shape.
+                       return;
+               }
+
+               protected override bool PreventGestureBubbling { get; set; } = true;
+
+               bool IImageVisualElementRenderer.IsDisposed => _disposed;
+
+               void OnButtonClick(object sender, RoutedEventArgs e)
+               {
+                       ((IButtonController)Element)?.SendReleased();
+                       ((IButtonController)Element)?.SendClicked();
+               }
+
+               void OnPointerPressed(object sender, RoutedEventArgs e)
+               {
+                       ((IButtonController)Element)?.SendPressed();
+               }
+
+               void UpdateBackground()
+               {
+                       Control.BackgroundColor = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBackgroundThemeBrush"];
+               }
+
+               void UpdateBorderColor()
+               {
+                       Control.BorderBrush = Element.BorderColor != Color.Default ? Element.BorderColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBorderThemeBrush"];
+               }
+
+               void UpdateBorderRadius()
+               {
+                       Control.BorderRadius = Element.CornerRadius;
+               }
+
+               void UpdateBorderWidth()
+               {
+                       Control.BorderThickness = Element.BorderWidth == (double)ImageButton.BorderWidthProperty.DefaultValue ? new WThickness(3) : new WThickness(Element.BorderWidth);
+               }
+
+               void IImageVisualElementRenderer.SetImage(Windows.UI.Xaml.Media.ImageSource image)
+               {
+                       _image.Source = image;
+               }
+
+               WImage IImageVisualElementRenderer.GetImage() =>
+                       Control?.Content as WImage;
+       }
+}
diff --git a/Xamarin.Forms.Platform.UAP/ImageElementManager.cs b/Xamarin.Forms.Platform.UAP/ImageElementManager.cs
new file mode 100644 (file)
index 0000000..e60dbae
--- /dev/null
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+       public static class ImageElementManager
+       {
+               public static void Init(IImageVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ElementChanged += OnElementChanged;
+                       renderer.ControlChanged += OnControlChanged;
+               }
+
+               internal static void Dispose(IImageVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       renderer.ElementChanged -= OnElementChanged;
+                       renderer.ControlChanged -= OnControlChanged;
+               }
+
+               static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+                       var controller = renderer.Element as IImageController;
+
+                       if (e.PropertyName == controller.AspectProperty?.PropertyName)
+                               UpdateAspect(renderer, controller);
+               }
+
+               static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       if (e.NewElement != null)
+                       {
+                               IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+                               var controller = renderer.Element as IImageController;
+
+                               UpdateAspect(renderer, controller);
+                       }
+               }
+
+
+
+               static void OnControlChanged(object sender, EventArgs e)
+               {
+                       IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+
+                       var controller = renderer.Element as IImageController;
+
+                       UpdateAspect(renderer, controller);
+               }
+
+               public static void UpdateAspect(IImageVisualElementRenderer renderer, IImageController controller)
+               {
+                       var Element = renderer.Element;
+                       var Control = renderer.GetNativeElement();
+                       var image = renderer.GetImage();
+
+                       if (renderer.IsDisposed || Element == null || Control == null)
+                       {
+                               return;
+                       }
+
+                       image.Stretch = GetStretch(controller.Aspect);
+                       if (controller.Aspect == Aspect.AspectFill || controller.Aspect == Aspect.AspectFit)
+
+                       {
+                               image.HorizontalAlignment = HorizontalAlignment.Center;
+                               image.VerticalAlignment = VerticalAlignment.Center;
+                       }
+                       else
+                       {
+                               image.HorizontalAlignment = HorizontalAlignment.Left;
+                               image.VerticalAlignment = VerticalAlignment.Top;
+                       }
+               }
+
+               static Stretch GetStretch(Aspect aspect)
+               {
+                       switch (aspect)
+                       {
+                               case Aspect.Fill:
+                                       return Stretch.Fill;
+                               case Aspect.AspectFill:
+                                       return Stretch.UniformToFill;
+                               default:
+                               case Aspect.AspectFit:
+                                       return Stretch.Uniform;
+                       }
+               }
+
+               public static async Task UpdateSource(IImageVisualElementRenderer renderer)
+               {
+                       var Element = renderer.Element;
+                       var Control = renderer.GetNativeElement();
+                       var controller = Element as IImageController;
+
+                       if (renderer.IsDisposed || Element == null || Control == null)
+                       {
+                               return;
+                       }
+
+                       controller.SetIsLoading(true);
+
+                       ImageSource source = controller.Source;
+                       IImageSourceHandler handler;
+                       if (source != null && (handler = Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+                       {
+                               Windows.UI.Xaml.Media.ImageSource imagesource;
+
+                               try
+                               {
+                                       imagesource = await handler.LoadImageAsync(source);
+                               }
+                               catch (OperationCanceledException)
+                               {
+                                       imagesource = null;
+                               }
+
+                               // In the time it takes to await the imagesource, some zippy little app
+                               // might have disposed of this Image already.
+                               if (Control != null)
+                               {
+                                       renderer.SetImage(imagesource);
+                               }
+
+                               RefreshImage(controller as IViewController);
+                       }
+                       else
+                       {
+                               renderer.SetImage(null);
+                               controller.SetIsLoading(false);
+                       }
+               }
+
+
+               static internal void RefreshImage(IViewController element)
+               {
+                       element?.InvalidateMeasure(InvalidationTrigger.RendererReady);
+               }
+
+
+       }
+}
index 986defa..d3d2692 100644 (file)
@@ -2,17 +2,25 @@
 using System.ComponentModel;
 using System.Threading.Tasks;
 using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
 using Windows.UI.Xaml.Media;
 using Windows.UI.Xaml.Media.Imaging;
 using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Platform.UWP
 {
-       public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>
+       public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>, IImageVisualElementRenderer
        {
                bool _measured;
                bool _disposed;
 
+               public ImageRenderer() : base()
+               {
+                       ImageElementManager.Init(this);
+               }
+
+               bool IImageVisualElementRenderer.IsDisposed => _disposed;
+
                public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
                {
                        if (Control.Source == null)
@@ -36,6 +44,7 @@ namespace Xamarin.Forms.Platform.UWP
 
                        if (disposing)
                        {
+                               ImageElementManager.Dispose(this);
                                if (Control != null)
                                {
                                        Control.ImageOpened -= OnImageOpened;
@@ -60,8 +69,7 @@ namespace Xamarin.Forms.Platform.UWP
                                        SetNativeControl(image);
                                }
 
-                               await TryUpdateSource();
-                               UpdateAspect();
+                               await TryUpdateSource().ConfigureAwait(false);
                        }
                }
 
@@ -70,30 +78,15 @@ namespace Xamarin.Forms.Platform.UWP
                        base.OnElementPropertyChanged(sender, e);
 
                        if (e.PropertyName == Image.SourceProperty.PropertyName)
-                               await TryUpdateSource();
-                       else if (e.PropertyName == Image.AspectProperty.PropertyName)
-                               UpdateAspect();
+                               await TryUpdateSource().ConfigureAwait(false);
                }
 
-               static Stretch GetStretch(Aspect aspect)
-               {
-                       switch (aspect)
-                       {
-                               case Aspect.Fill:
-                                       return Stretch.Fill;
-                               case Aspect.AspectFill:
-                                       return Stretch.UniformToFill;
-                               default:
-                               case Aspect.AspectFit:
-                                       return Stretch.Uniform;
-                       }
-               }
 
                void OnImageOpened(object sender, RoutedEventArgs routedEventArgs)
                {
                        if (_measured)
                        {
-                               RefreshImage();
+                               ImageElementManager.RefreshImage(Element);
                        }
 
                        Element?.SetIsLoading(false);
@@ -101,34 +94,10 @@ namespace Xamarin.Forms.Platform.UWP
 
                protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
                {
-                       Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}" );
+                       Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}");
                        Element?.SetIsLoading(false);
                }
 
-               void RefreshImage()
-               {
-                       ((IVisualElementController)Element)?.InvalidateMeasure(InvalidationTrigger.RendererReady);
-               }
-
-               void UpdateAspect()
-               {
-                       if (_disposed || Element == null || Control == null)
-                       {
-                               return;
-                       }
-
-                       Control.Stretch = GetStretch(Element.Aspect);
-                       if (Element.Aspect == Aspect.AspectFill || Element.Aspect == Aspect.AspectFit) // Then Center Crop
-                       {
-                               Control.HorizontalAlignment = HorizontalAlignment.Center;
-                               Control.VerticalAlignment = VerticalAlignment.Center;
-                       }
-                       else // Default
-                       {
-                               Control.HorizontalAlignment = HorizontalAlignment.Left;
-                               Control.VerticalAlignment = VerticalAlignment.Top;
-                       }
-               }
 
                protected virtual async Task TryUpdateSource()
                {
@@ -152,42 +121,13 @@ namespace Xamarin.Forms.Platform.UWP
 
                protected async Task UpdateSource()
                {
-                       if (_disposed || Element == null || Control == null)
-                       {
-                               return;
-                       }
-
-                       Element.SetIsLoading(true);
-
-                       ImageSource source = Element.Source;
-                       IImageSourceHandler handler;
-                       if (source != null && (handler = Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
-                       {
-                               Windows.UI.Xaml.Media.ImageSource imagesource;
-
-                               try
-                               {
-                                       imagesource = await handler.LoadImageAsync(source);
-                               }
-                               catch (OperationCanceledException)
-                               {
-                                       imagesource = null;
-                               }
-
-                               // In the time it takes to await the imagesource, some zippy little app
-                               // might have disposed of this Image already.
-                               if (Control != null)
-                               {
-                                       Control.Source = imagesource;
-                               }
+                       await ImageElementManager.UpdateSource(this).ConfigureAwait(false);             }
 
-                               RefreshImage();
-                       }
-                       else
-                       {
-                               Control.Source = null;
-                               Element.SetIsLoading(false);
-                       }
+               void IImageVisualElementRenderer.SetImage(Windows.UI.Xaml.Media.ImageSource image)
+               {
+                       Control.Source = image;
                }
+
+               Windows.UI.Xaml.Controls.Image IImageVisualElementRenderer.GetImage() => Control;
        }
 }
index dd785dc..f4b4fb6 100644 (file)
@@ -10,6 +10,7 @@ using Xamarin.Forms.Platform.UWP;
 [assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))]
 [assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))]
 [assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))]
+[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
 [assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
 [assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
 [assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
index e18cbd2..3ed6a6d 100644 (file)
@@ -12,7 +12,7 @@ using Windows.UI.Xaml.Input;
 
 namespace Xamarin.Forms.Platform.UWP
 {
-       public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualElementRenderer, IDisposable, IEffectControlProvider where TElement : VisualElement
+       public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualNativeElementRenderer, IDisposable, IEffectControlProvider where TElement : VisualElement
                                                                                                                                                                                                                                                                          where TNativeElement : FrameworkElement
        {
                string _defaultAutomationPropertiesName;
@@ -22,6 +22,9 @@ namespace Xamarin.Forms.Platform.UWP
                bool _disposed;
                FocusNavigationDirection focusDirection;
                EventHandler<VisualElementChangedEventArgs> _elementChangedHandlers;
+               event EventHandler<PropertyChangedEventArgs> _elementPropertyChanged;
+               event EventHandler _controlChanging;
+               event EventHandler _controlChanged;
                VisualElementTracker<TElement, TNativeElement> _tracker;
                Windows.UI.Xaml.Controls.Page _containingPage; // Cache of containing page used for unfocusing
                Control _control => Control as Control;
@@ -138,7 +141,7 @@ namespace Xamarin.Forms.Platform.UWP
 
                                if (AutoTrack && Tracker == null)
                                {
-                                       Tracker = new VisualElementTracker<TElement, TNativeElement>(); 
+                                       Tracker = new VisualElementTracker<TElement, TNativeElement>();
                                }
 
                                // Disabled until reason for crashes with unhandled exceptions is discovered
@@ -178,6 +181,22 @@ namespace Xamarin.Forms.Platform.UWP
                }
 
                public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged;
+               event EventHandler<PropertyChangedEventArgs> IVisualNativeElementRenderer.ElementPropertyChanged
+               {
+                       add => _elementPropertyChanged += value;
+                       remove => _elementPropertyChanged -= value;
+               }
+
+               event EventHandler IVisualNativeElementRenderer.ControlChanging
+               {
+                       add { _controlChanging += value; }
+                       remove { _controlChanging -= value; }
+               }
+               event EventHandler IVisualNativeElementRenderer.ControlChanged
+               {
+                       add { _controlChanged += value; }
+                       remove { _controlChanged -= value; }
+               }
 
                protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
                {
@@ -347,7 +366,7 @@ namespace Xamarin.Forms.Platform.UWP
                                SetAutomationPropertiesAccessibilityView();
                        else if (e.PropertyName == AutomationProperties.LabeledByProperty.PropertyName)
                                SetAutomationPropertiesLabeledBy();
-                       else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName || 
+                       else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName ||
                                        e.PropertyName == Layout.CascadeInputTransparentProperty.PropertyName)
                                UpdateInputTransparent();
                        if (e.PropertyName == Specifics.AccessKeyProperty.PropertyName ||
@@ -359,6 +378,8 @@ namespace Xamarin.Forms.Platform.UWP
                                UpdateTabStop();
                        else if (e.PropertyName == VisualElement.TabIndexProperty.PropertyName)
                                UpdateTabIndex();
+
+                       _elementPropertyChanged?.Invoke(this, e);
                }
 
                protected virtual void OnRegisterEffect(PlatformEffect effect)
@@ -443,6 +464,7 @@ namespace Xamarin.Forms.Platform.UWP
 
                protected void SetNativeControl(TNativeElement control)
                {
+                       _controlChanging?.Invoke(this, EventArgs.Empty);
                        TNativeElement oldControl = Control;
                        Control = control;
 
@@ -458,7 +480,10 @@ namespace Xamarin.Forms.Platform.UWP
                        UpdateTracker();
 
                        if (control == null)
+                       {
+                               _controlChanged?.Invoke(this, EventArgs.Empty);
                                return;
+                       }
 
                        Control.HorizontalAlignment = HorizontalAlignment.Stretch;
                        Control.VerticalAlignment = VerticalAlignment.Stretch;
@@ -478,6 +503,8 @@ namespace Xamarin.Forms.Platform.UWP
 
                        if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
                                SetAutomationId(Element.AutomationId);
+
+                       _controlChanged?.Invoke(this, EventArgs.Empty);
                }
 
                protected virtual void UpdateBackgroundColor()
index 1cfdad1..8c04c5b 100644 (file)
@@ -43,6 +43,8 @@
     </NoWarn>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="IImageVisualElementRenderer.cs" />
+    <Compile Include="ImageButtonRenderer.cs" />
     <Compile Include="ColorExtensions.cs" />
     <Compile Include="FormsCancelButton.cs" />
     <Compile Include="AlertDialog.cs" />
@@ -55,6 +57,7 @@
     <Compile Include="FormsTextBox.cs" />
     <Compile Include="FormsComboBox.cs" />
     <Compile Include="ITabStopOnDescendants.cs" />
+    <Compile Include="ImageElementManager.cs" />
     <Compile Include="InterceptVisualStateManager.cs" />
     <Compile Include="HorizontalTextAlignmentConverter.cs" />
     <Compile Include="ITitleIconProvider.cs" />
@@ -62,6 +65,7 @@
     <Compile Include="ITitleViewProvider.cs" />
     <Compile Include="ITitleViewRendererController.cs" />
     <Compile Include="IToolbarProvider.cs" />
+    <Compile Include="IVisualNativeElementRenderer.cs" />
     <Compile Include="NativeBindingExtensions.cs" />
     <Compile Include="NativeEventWrapper.cs" />
     <Compile Include="NativePropertyListener.cs" />
diff --git a/Xamarin.Forms.Platform.iOS/IVisualNativeElementRenderer.cs b/Xamarin.Forms.Platform.iOS/IVisualNativeElementRenderer.cs
new file mode 100644 (file)
index 0000000..d91385c
--- /dev/null
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+#if __MOBILE__
+using NativeColor = UIKit.UIColor;
+using NativeControl = UIKit.UIControl;
+using NativeView = UIKit.UIView;
+
+namespace Xamarin.Forms.Platform.iOS
+#else
+using NativeView = AppKit.NSView;
+using NativeColor = CoreGraphics.CGColor;
+using NativeControl = AppKit.NSControl;
+namespace Xamarin.Forms.Platform.MacOS
+#endif
+{
+       public interface IVisualNativeElementRenderer : IVisualElementRenderer
+       {
+               event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+               event EventHandler ControlChanging;
+               event EventHandler ControlChanged;
+
+               NativeView Control { get; }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/BorderElementManager.cs b/Xamarin.Forms.Platform.iOS/Renderers/BorderElementManager.cs
new file mode 100644 (file)
index 0000000..45154b1
--- /dev/null
@@ -0,0 +1,72 @@
+using System;
+using System.ComponentModel;
+using NativeView = UIKit.UIView;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       internal static class BorderElementManager
+       {
+               static nfloat _defaultCornerRadius = 5;
+
+               public static void Init(IVisualNativeElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ElementChanged += OnElementChanged;
+                       renderer.ControlChanged += OnControlChanged;
+               }
+
+               public static void Dispose(IVisualNativeElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       renderer.ElementChanged -= OnElementChanged;
+                       renderer.ControlChanged -= OnControlChanged;
+               }
+
+               static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       IVisualNativeElementRenderer renderer = (IVisualNativeElementRenderer)sender;
+                       IBorderController backgroundView = (IBorderController)renderer.Element;
+
+                       if (e.PropertyName == backgroundView.BorderWidthProperty.PropertyName || e.PropertyName == backgroundView.CornerRadiusProperty.PropertyName || e.PropertyName == backgroundView.BorderColorProperty.PropertyName)
+                               UpdateBorder(renderer, backgroundView);
+               }
+
+               static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       if (e.NewElement != null)
+                       {
+                               UpdateBorder((IVisualNativeElementRenderer)sender, (IBorderController)e.NewElement);
+                       }
+               }
+
+               public static void UpdateBorder(IVisualNativeElementRenderer renderer, IBorderController backgroundView)
+               {
+                       var control = renderer.Control;
+                       var ImageButton = backgroundView;
+
+                       if (control == null)
+                       {
+                               return;
+                       }
+
+                       if (ImageButton.BorderColor != Color.Default)
+                               control.Layer.BorderColor = ImageButton.BorderColor.ToCGColor();
+
+                       control.Layer.BorderWidth = Math.Max(0f, (float)ImageButton.BorderWidth);
+
+                       nfloat cornerRadius = _defaultCornerRadius;
+
+                       if (ImageButton.IsSet(ImageButton.CornerRadiusProperty) && ImageButton.CornerRadius != (int)ImageButton.CornerRadiusProperty.DefaultValue)
+                               cornerRadius = ImageButton.CornerRadius;
+
+                       control.Layer.CornerRadius = cornerRadius;
+               }
+
+               static void OnControlChanged(object sender, EventArgs e)
+               {
+                       IVisualNativeElementRenderer renderer = (IVisualNativeElementRenderer)sender;
+                       IBorderController backgroundView = (IBorderController)renderer.Element;
+                       UpdateBorder(renderer, backgroundView);
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ButtonElementManager.cs b/Xamarin.Forms.Platform.iOS/Renderers/ButtonElementManager.cs
new file mode 100644 (file)
index 0000000..4ce29b0
--- /dev/null
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+using Foundation;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       internal static class ButtonElementManager
+       {
+               static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
+
+               public static void Init(IVisualNativeElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ControlChanged += OnControlChanged;
+               }
+
+               static void OnControlChanged(object sender, EventArgs e)
+               {
+                       var renderer = (IVisualNativeElementRenderer)sender;
+                       var control = (UIButton)renderer.Control;
+
+                       foreach (UIControlState uiControlState in s_controlStates)
+                       {
+                               control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
+                               control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
+                               control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
+                       }
+
+
+                       control.TouchUpInside -= TouchUpInside;
+                       control.TouchDown -= TouchDown;
+                       control.TouchUpInside += TouchUpInside;
+                       control.TouchDown += TouchDown;
+               }
+
+               static void TouchUpInside(object sender, EventArgs eventArgs)
+               {
+                       var button = sender as UIButton;
+                       var renderer = button.Superview as IVisualNativeElementRenderer;
+                       OnButtonTouchUpInside(renderer.Element as IButtonController);
+               }
+
+               static void TouchDown(object sender, EventArgs eventArgs)
+               {
+                       var button = sender as UIButton;
+                       var renderer = button.Superview as IVisualNativeElementRenderer;
+                       OnButtonTouchDown(renderer.Element as IButtonController);
+               }
+
+               public static void Dispose(IVisualNativeElementRenderer renderer)
+               {
+                       var control = (UIButton)renderer.Control;
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       control.TouchUpInside -= TouchUpInside;
+                       control.TouchDown -= TouchDown;
+               }
+
+               static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+               }
+
+
+               static void SetControlPropertiesFromProxy(UIButton control)
+               {
+                       foreach (UIControlState uiControlState in s_controlStates)
+                       {
+                               control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
+                               control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
+                               control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
+                       }
+               }
+
+               internal static void OnButtonTouchDown(IButtonController element)
+               {
+                       element?.SendPressed();
+               }
+
+               internal static void OnButtonTouchUpInside(IButtonController element)
+               {
+                       element?.SendReleased();
+                       element?.SendClicked();
+               }
+       }
+}
\ No newline at end of file
index 517123f..4b0b4b4 100644 (file)
@@ -9,8 +9,9 @@ using SizeF = CoreGraphics.CGSize;
 
 namespace Xamarin.Forms.Platform.iOS
 {
-       public class ButtonRenderer : ViewRenderer<Button, UIButton>
+       public class ButtonRenderer : ViewRenderer<Button, UIButton>, IImageVisualElementRenderer
        {
+               bool _isDisposed;
                UIColor _buttonTextColorDefaultDisabled;
                UIColor _buttonTextColorDefaultHighlighted;
                UIColor _buttonTextColorDefaultNormal;
@@ -24,18 +25,25 @@ namespace Xamarin.Forms.Platform.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
-               readonly nfloat _defaultCornerRadius = 5;
+               readonly nfloat _minimumButtonHeight = 44; // Apple docs 
 
                static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
 
+               public bool IsDisposed => _isDisposed;
+
+               protected ButtonRenderer() : base()
+               {
+                       BorderElementManager.Init(this);
+                       ImageElementManager.Init(this);
+               }
+
                public override SizeF SizeThatFits(SizeF size)
                {
                        var result = base.SizeThatFits(size);
 
                        if (result.Height < _minimumButtonHeight)
                        {
-                               result.Height = _minimumButtonHeight; 
+                               result.Height = _minimumButtonHeight;
                        }
 
                        return result;
@@ -43,12 +51,18 @@ namespace Xamarin.Forms.Platform.iOS
 
                protected override void Dispose(bool disposing)
                {
+                       if (_isDisposed)
+                               return;
                        if (Control != null)
                        {
                                Control.TouchUpInside -= OnButtonTouchUpInside;
                                Control.TouchDown -= OnButtonTouchDown;
+                               BorderElementManager.Dispose(this);
+                               ImageElementManager.Dispose(this);
                        }
 
+                       _isDisposed = true;
+
                        base.Dispose(disposing);
                }
 
@@ -78,7 +92,6 @@ namespace Xamarin.Forms.Platform.iOS
 
                                UpdateText();
                                UpdateFont();
-                               UpdateBorder();
                                UpdateImage();
                                UpdateTextColor();
                                UpdatePadding();
@@ -100,14 +113,12 @@ namespace Xamarin.Forms.Platform.iOS
                                UpdateTextColor();
                        else if (e.PropertyName == Button.FontProperty.PropertyName)
                                UpdateFont();
-                       else if (e.PropertyName == Button.BorderWidthProperty.PropertyName || e.PropertyName == Button.CornerRadiusProperty.PropertyName || e.PropertyName == Button.BorderColorProperty.PropertyName)
-                               UpdateBorder();
                        else if (e.PropertyName == Button.ImageProperty.PropertyName)
                                UpdateImage();
                        else if (e.PropertyName == Button.PaddingProperty.PropertyName)
                                UpdatePadding();
                }
-    
+
                protected override void SetAccessibilityLabel()
                {
                        // If we have not specified an AccessibilityLabel and the AccessibiltyLabel is current bound to the Title,
@@ -122,7 +133,7 @@ namespace Xamarin.Forms.Platform.iOS
 
                        base.SetAccessibilityLabel();
                }
-               
+
                void SetControlPropertiesFromProxy()
                {
                        foreach (UIControlState uiControlState in s_controlStates)
@@ -135,31 +146,12 @@ namespace Xamarin.Forms.Platform.iOS
 
                void OnButtonTouchUpInside(object sender, EventArgs eventArgs)
                {
-                       ((IButtonController)Element)?.SendReleased();
-                       ((IButtonController)Element)?.SendClicked();
+                       ButtonElementManager.OnButtonTouchUpInside(this.Element);
                }
 
                void OnButtonTouchDown(object sender, EventArgs eventArgs)
                {
-                       ((IButtonController)Element)?.SendPressed();
-               }
-
-               void UpdateBorder()
-               {
-                       var uiButton = Control;
-                       var button = Element;
-
-                       if (button.BorderColor != Color.Default)
-                               uiButton.Layer.BorderColor = button.BorderColor.ToCGColor();
-
-                       uiButton.Layer.BorderWidth = Math.Max(0f, (float)button.BorderWidth);
-
-                       nfloat cornerRadius = _defaultCornerRadius;
-
-                       if (button.IsSet(Button.CornerRadiusProperty) && button.CornerRadius != (int)Button.CornerRadiusProperty.DefaultValue)
-                               cornerRadius = button.CornerRadius;
-
-                       uiButton.Layer.CornerRadius = cornerRadius;
+                       ButtonElementManager.OnButtonTouchDown(this.Element);
                }
 
                void UpdateFont()
@@ -169,37 +161,34 @@ namespace Xamarin.Forms.Platform.iOS
 
                async void UpdateImage()
                {
-                       IImageSourceHandler handler;
-                       FileImageSource source = Element.Image;
-                       if (source != null && (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+                       try
                        {
-                               UIImage uiimage;
-                               try
-                               {
-                                       uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
-                               }
-                               catch (OperationCanceledException)
-                               {
-                                       uiimage = null;
-                               }
-                               UIButton button = Control;
-                               if (button != null && uiimage != null)
-                               {
-                                       button.SetImage(uiimage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
-
-                                       button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+                               await ImageElementManager.SetImage(this, Element);
+                       }
+                       catch (Exception ex)
+                       {
+                               Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+                       }
+               }
 
-                                       ComputeEdgeInsets(Control, Element.ContentLayout);
-                               }
+               public void SetImage(UIImage image)
+               {
+                       if (image != null)
+                       {
+                               UIButton button = Control;
+                               button.SetImage(image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
+                               button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+                               ComputeEdgeInsets(Control, Element.ContentLayout);
                        }
                        else
                        {
                                Control.SetImage(null, UIControlState.Normal);
                                ClearEdgeInsets(Control);
                        }
-                       ((IVisualElementController)Element).NativeSizeChanged();
                }
 
+               public UIImageView GetImage() => Control?.ImageView;
+
                void UpdateText()
                {
                        var newText = Element.Text;
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/IImageVisualElementRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/IImageVisualElementRenderer.cs
new file mode 100644 (file)
index 0000000..59f9b24
--- /dev/null
@@ -0,0 +1,11 @@
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       public interface IImageVisualElementRenderer : IVisualNativeElementRenderer
+       {
+               void SetImage(UIImage image);
+               bool IsDisposed { get; }
+               UIImageView GetImage();
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ImageButtonRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ImageButtonRenderer.cs
new file mode 100644 (file)
index 0000000..fcf3982
--- /dev/null
@@ -0,0 +1,146 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Foundation;
+using UIKit;
+using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
+using SizeF = CoreGraphics.CGSize;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       public class ImageButtonRenderer : ViewRenderer<ImageButton, UIButton>, IImageVisualElementRenderer
+       {
+               bool _isDisposed;
+
+               // 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 ImageButtonRenderer() : base()
+               {
+                       ButtonElementManager.Init(this);
+                       BorderElementManager.Init(this);
+                       ImageElementManager.Init(this);
+               }
+
+               public override SizeF SizeThatFits(SizeF size)
+               {
+                       var result = base.SizeThatFits(size);
+
+                       if (result.Height < _minimumButtonHeight)
+                       {
+                               result.Height = _minimumButtonHeight;
+                       }
+
+                       return result;
+               }
+
+               protected override void Dispose(bool disposing)
+               {
+                       if (_isDisposed)
+                               return;
+
+                       if (disposing && Control != null)
+                       {
+                               ButtonElementManager.Dispose(this);
+                               BorderElementManager.Dispose(this);
+                               ImageElementManager.Dispose(this);
+                       }
+
+                       _isDisposed = true;
+                       base.Dispose(disposing);
+               }
+
+               protected async override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       base.OnElementPropertyChanged(sender, e);
+
+                       if (e.PropertyName == ImageButton.SourceProperty.PropertyName)
+                               await ImageElementManager.SetImage(this, Element).ConfigureAwait(false);
+                       else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+                               UpdatePadding();
+               }
+
+               protected async override void OnElementChanged(ElementChangedEventArgs<ImageButton> e)
+               {
+                       base.OnElementChanged(e);
+
+                       if (e.NewElement != null)
+                       {
+                               if (Control == null)
+                               {
+                                       SetNativeControl(CreateNativeControl());
+
+                                       Debug.Assert(Control != null, "Control != null");
+                               }
+
+                               UpdatePadding();
+                               await UpdateImage().ConfigureAwait(false);
+                       }
+               }
+
+               void UpdatePadding(UIButton button = null)
+               {
+                       var uiElement = button ?? Control;
+                       if (uiElement == null)
+                               return;
+
+                       uiElement.ContentEdgeInsets = new UIEdgeInsets(
+                               (float)(Element.Padding.Top),
+                               (float)(Element.Padding.Left),
+                               (float)(Element.Padding.Bottom),
+                               (float)(Element.Padding.Right)
+                       );
+               }
+               async Task UpdateImage()
+               {
+                       try
+                       {
+                               await ImageElementManager.SetImage(this, Element).ConfigureAwait(false);
+                       }
+                       catch (Exception ex)
+                       {
+                               Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+                       }
+               }
+
+               protected override UIButton CreateNativeControl()
+               {
+                       return new UIButton(UIButtonType.System);
+               }
+
+               protected override void SetAccessibilityLabel()
+               {
+                       // If we have not specified an AccessibilityLabel and the AccessibiltyLabel is current bound to the Title,
+                       // exit this method so we don't set the AccessibilityLabel value and break the binding.
+                       // This may pose a problem for users who want to explicitly set the AccessibilityLabel to null, but this
+                       // will prevent us from inadvertently breaking UI Tests that are using Query.Marked to get the dynamic Title 
+                       // of the ImageButton.
+
+                       var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
+                       if (string.IsNullOrWhiteSpace(elemValue) && Control?.AccessibilityLabel == Control?.Title(UIControlState.Normal))
+                               return;
+
+                       base.SetAccessibilityLabel();
+               }
+
+               bool IImageVisualElementRenderer.IsDisposed => _isDisposed;
+               void IImageVisualElementRenderer.SetImage(UIImage image)
+               {
+                       Control.SetImage(image?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
+                       Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Fill;
+                       Control.VerticalAlignment = UIControlContentVerticalAlignment.Fill;
+               }
+
+               UIImageView IImageVisualElementRenderer.GetImage()
+               {
+                       return Control?.ImageView;
+               }
+       }
+}
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs b/Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs
new file mode 100644 (file)
index 0000000..0a44cb9
--- /dev/null
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Foundation;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       public static class ImageElementManager
+       {
+               public static void Init(IImageVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged += OnElementPropertyChanged;
+                       renderer.ElementChanged += OnElementChanged;
+                       renderer.ControlChanged += OnControlChanged;
+               }
+
+               public static void Dispose(IImageVisualElementRenderer renderer)
+               {
+                       renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+                       renderer.ElementChanged -= OnElementChanged;
+                       renderer.ControlChanged -= OnControlChanged;
+               }
+
+
+               static void OnControlChanged(object sender, EventArgs e)
+               {
+                       var renderer = sender as IImageVisualElementRenderer;
+                       var imageElement = renderer.Element as IImageController;
+                       SetAspect(renderer, imageElement);
+                       SetOpacity(renderer, imageElement);
+               }
+
+               static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+               {
+                       if (e.NewElement != null)
+                       {
+                               var renderer = sender as IImageVisualElementRenderer;
+                               var imageElement = renderer.Element as IImageController;
+
+                               SetAspect(renderer, imageElement);
+                               SetOpacity(renderer, imageElement);
+                       }
+               }
+
+               static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               {
+                       var renderer = sender as IImageVisualElementRenderer;
+                       var imageElement = renderer.Element as IImageController;
+
+                       if (e.PropertyName == imageElement.IsOpaqueProperty?.PropertyName)
+                               SetOpacity(renderer, renderer.Element as IImageController);
+                       else if (e.PropertyName == imageElement.AspectProperty?.PropertyName)
+                               SetAspect(renderer, renderer.Element as IImageController);
+               }
+
+
+
+               public static void SetAspect(IImageVisualElementRenderer renderer, IImageController imageElement)
+               {
+                       var Element = renderer.Element;
+                       var Control = renderer.GetImage();
+
+
+                       if (renderer.IsDisposed || Element == null || Control == null)
+                       {
+                               return;
+                       }
+
+                       Control.ContentMode = imageElement.Aspect.ToUIViewContentMode();
+               }
+
+               public static void SetOpacity(IImageVisualElementRenderer renderer, IImageController imageElement)
+               {
+                       var Element = renderer.Element;
+                       var Control = renderer.GetImage();
+
+                       if (renderer.IsDisposed || Element == null || Control == null)
+                       {
+                               return;
+                       }
+
+                       Control.Opaque = imageElement.IsOpaque;
+               }
+
+               public static async Task SetImage(IImageVisualElementRenderer renderer, IImageController imageElement, Image oldElement = null)
+               {
+                       _ = renderer ?? throw new ArgumentNullException($"{nameof(ImageElementManager)}.{nameof(SetImage)} {nameof(renderer)} cannot be null");
+                       _ = imageElement ?? throw new ArgumentNullException($"{nameof(ImageElementManager)}.{nameof(SetImage)} {nameof(imageElement)} cannot be null");
+
+                       var Element = renderer.Element;
+                       var Control = renderer.GetImage();
+
+                       if (renderer.IsDisposed || Element == null || Control == null)
+                       {
+                               return;
+                       }
+
+                       var source = imageElement.Source;
+
+                       if (oldElement != null)
+                       {
+                               var oldSource = oldElement.Source;
+                               if (Equals(oldSource, source))
+                                       return;
+
+                               if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
+                                       return;
+
+                               renderer.SetImage(null);
+                       }
+
+                       IImageSourceHandler handler;
+                       imageElement.SetIsLoading(true);
+                       try
+                       {
+                               if (source != null &&
+                                  (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+                               {
+                                       UIImage uiimage;
+                                       try
+                                       {
+                                               uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
+                                       }
+                                       catch (OperationCanceledException)
+                                       {
+                                               uiimage = null;
+                                       }
+
+                                       if (renderer.IsDisposed)
+                                               return;
+
+                                       var imageView = Control;
+                                       if (imageView != null)
+                                       {
+                                               renderer.SetImage(uiimage);
+                                       }
+                               }
+                               else
+                               {
+                                       renderer.SetImage(null);
+                               }
+
+                       }
+                       finally
+                       {
+                               imageElement.SetIsLoading(false);
+                       }
+
+                       (imageElement as IViewController)?.NativeSizeChanged();
+               }
+       }
+}
\ No newline at end of file
index 844209c..a3476d9 100644 (file)
@@ -27,10 +27,15 @@ namespace Xamarin.Forms.Platform.iOS
                }
        }
 
-       public class ImageRenderer : ViewRenderer<Image, UIImageView>
+       public class ImageRenderer : ViewRenderer<Image, UIImageView>, IImageVisualElementRenderer
        {
                bool _isDisposed;
 
+               public ImageRenderer() : base()
+               {
+                       ImageElementManager.Init(this);
+               }
+
                protected override void Dispose(bool disposing)
                {
                        if (_isDisposed)
@@ -41,6 +46,7 @@ namespace Xamarin.Forms.Platform.iOS
                                UIImage oldUIImage;
                                if (Control != null && (oldUIImage = Control.Image) != null)
                                {
+                                       ImageElementManager.Dispose(this);
                                        oldUIImage.Dispose();
                                }
                        }
@@ -62,9 +68,7 @@ namespace Xamarin.Forms.Platform.iOS
 
                        if (e.NewElement != null)
                        {
-                               SetAspect();
-                               await TrySetImage(e.OldElement);
-                               SetOpacity();
+                               await TrySetImage(e.OldElement as Image);
                        }
 
                        base.OnElementChanged(e);
@@ -73,22 +77,9 @@ namespace Xamarin.Forms.Platform.iOS
                protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                        base.OnElementPropertyChanged(sender, e);
-                       if (e.PropertyName == Image.SourceProperty.PropertyName)
-                               await TrySetImage();
-                       else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
-                               SetOpacity();
-                       else if (e.PropertyName == Image.AspectProperty.PropertyName)
-                               SetAspect();
-               }
 
-               void SetAspect()
-               {
-                       if (_isDisposed || Element == null || Control == null)
-                       {
-                               return;
-                       }
-
-                       Control.ContentMode = Element.Aspect.ToUIViewContentMode();
+                       if (e.PropertyName == Image.SourceProperty.PropertyName)
+                               await TrySetImage().ConfigureAwait(false);
                }
 
                protected virtual async Task TrySetImage(Image previous = null)
@@ -113,70 +104,17 @@ namespace Xamarin.Forms.Platform.iOS
 
                protected async Task SetImage(Image oldElement = null)
                {
-                       if (_isDisposed || Element == null || Control == null)
-                       {
-                               return;
-                       }
-
-                       var source = Element.Source;
-
-                       if (oldElement != null)
-                       {
-                               var oldSource = oldElement.Source;
-                               if (Equals(oldSource, source))
-                                       return;
-
-                               if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
-                                       return;
-
-                               Control.Image = null;
-                       }
-
-                       IImageSourceHandler handler;
-
-                       Element.SetIsLoading(true);
-
-                       if (source != null &&
-                           (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
-                       {
-                               UIImage uiimage;
-                               try
-                               {
-                                       uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
-                               }
-                               catch (OperationCanceledException)
-                               {
-                                       uiimage = null;
-                               }
-
-                               if (_isDisposed)
-                                       return;
-
-                               var imageView = Control;
-                               if (imageView != null)
-                                       imageView.Image = uiimage;
-
-                               ((IVisualElementController)Element).NativeSizeChanged();
-                       }
-                       else
-                       {
-                               Control.Image = null;
-                       }
-
-                       Element.SetIsLoading(false);
+                       await ImageElementManager.SetImage(this, Element, oldElement).ConfigureAwait(false);
                }
 
-               void SetOpacity()
-               {
-                       if (_isDisposed || Element == null || Control == null)
-                       {
-                               return;
-                       }
+               void IImageVisualElementRenderer.SetImage(UIImage image) => Control.Image = image;
 
-                       Control.Opaque = Element.IsOpaque;
-               }
+               bool IImageVisualElementRenderer.IsDisposed => _isDisposed;
+
+               UIImageView IImageVisualElementRenderer.GetImage() => Control;
        }
 
+
        public interface IImageSourceHandler : IRegisterable
        {
                Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1);
index 77a3951..d238cac 100644 (file)
@@ -27,7 +27,7 @@ namespace Xamarin.Forms.Platform.MacOS
        {
        }
 
-       public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, ITabStop where TView : View where TNativeView : NativeView
+       public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, IVisualNativeElementRenderer, ITabStop where TView : View where TNativeView : NativeView
        {
 #if __MOBILE__
                string _defaultAccessibilityLabel;
@@ -36,12 +36,38 @@ namespace Xamarin.Forms.Platform.MacOS
 #endif
                NativeColor _defaultColor;
 
+               event EventHandler<PropertyChangedEventArgs> _elementPropertyChanged;
+               event EventHandler _controlChanging;
+               event EventHandler _controlChanged;
+
+
+
                protected virtual TNativeView CreateNativeControl()
                {
                        return default(TNativeView);
                }
 
                public TNativeView Control { get; private set; }
+               NativeView IVisualNativeElementRenderer.Control => Control;
+
+
+               event EventHandler<PropertyChangedEventArgs> IVisualNativeElementRenderer.ElementPropertyChanged
+               {
+                       add { _elementPropertyChanged += value; }
+                       remove { _elementPropertyChanged -= value; }
+               }
+
+               event EventHandler IVisualNativeElementRenderer.ControlChanging
+               {
+                       add { _controlChanging += value; }
+                       remove { _controlChanging -= value; }
+               }
+               event EventHandler IVisualNativeElementRenderer.ControlChanged
+               {
+                       add { _controlChanged += value; }
+                       remove { _controlChanged -= value; }
+               }
+
 
                NativeView ITabStop.TabStop => Control;
 #if __MOBILE__
@@ -123,6 +149,7 @@ namespace Xamarin.Forms.Platform.MacOS
                        }
 
                        base.OnElementPropertyChanged(sender, e);
+                       _elementPropertyChanged?.Invoke(this, e);
                }
 
                protected override void OnRegisterEffect(PlatformEffect effect)
@@ -210,6 +237,7 @@ namespace Xamarin.Forms.Platform.MacOS
 
                protected void SetNativeControl(TNativeView uiview)
                {
+                       _controlChanging?.Invoke(this, EventArgs.Empty);
 #if __MOBILE__
                        _defaultColor = uiview.BackgroundColor;
 
@@ -232,6 +260,8 @@ namespace Xamarin.Forms.Platform.MacOS
                        UpdateFlowDirection();
 
                        AddSubview(uiview);
+
+                       _controlChanged?.Invoke(this, EventArgs.Empty);
                }
 
 #if __MOBILE__
index 8a56a6a..7c2b89c 100644 (file)
@@ -129,6 +129,8 @@ namespace Xamarin.Forms.Platform.MacOS
                        remove { _elementChangedHandlers.Remove(value); }
                }
 
+
+
                public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
                {
                        return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
@@ -136,6 +138,9 @@ namespace Xamarin.Forms.Platform.MacOS
 
                public NativeView NativeView => this;
 
+
+               protected internal virtual NativeView GetControl() => NativeView;
+
                void IVisualElementRenderer.SetElement(VisualElement element)
                {
                        SetElement((TElement)element);
@@ -376,6 +381,7 @@ namespace Xamarin.Forms.Platform.MacOS
                        else if (e.PropertyName == AutomationProperties.IsInAccessibleTreeProperty.PropertyName)
                                SetIsAccessibilityElement();
 #endif
+
                }
 
                protected virtual void OnRegisterEffect(PlatformEffect effect)
index f6e8baf..05065cb 100644 (file)
     <Compile Include="Extensions\LabelExtensions.cs" />
     <Compile Include="Extensions\VisualElementExtensions.cs" />
     <Compile Include="Flags.cs" />
+    <Compile Include="Renderers\ImageElementManager.cs" />
+    <Compile Include="IVisualNativeElementRenderer.cs" />
     <Compile Include="NativeViewWrapper.cs" />
     <Compile Include="NativeViewWrapperRenderer.cs" />
     <Compile Include="PlatformEffect.cs" />
     <Compile Include="Forms.cs" />
     <Compile Include="PageExtensions.cs" />
     <Compile Include="Renderers\WkWebViewRenderer.cs" />
+    <Compile Include="Renderers\BorderElementManager.cs" />
+    <Compile Include="Renderers\ButtonElementManager.cs" />
+    <Compile Include="Renderers\IImageVisualElementRenderer.cs" />
+    <Compile Include="Renderers\ImageButtonRenderer.cs" />
     <Compile Include="Resources\StringResources.Designer.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
index 4ddfc19..565da32 100644 (file)
        {
        }
 
+       internal class _ImageButtonRenderer
+       {
+       }
+
        internal class _ButtonRenderer
        {
        }