maui-linux/Xamarin.Forms.Platform.iOS/Renderers/ButtonLayoutManager.cs

354 строки
9.7 KiB
C#

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using CoreGraphics;
using UIKit;
namespace Xamarin.Forms.Platform.iOS
{
// TODO: The entire layout system. iOS buttons were not designed for
// anything but image left, text right, single line layouts.
public class ButtonLayoutManager : IDisposable
{
bool _disposed;
IButtonLayoutRenderer _renderer;
Button _element;
bool _preserveInitialPadding;
bool _spacingAdjustsPadding;
bool _borderAdjustsPadding;
bool _collapseHorizontalPadding;
UIEdgeInsets? _defaultImageInsets;
UIEdgeInsets? _defaultTitleInsets;
UIEdgeInsets? _defaultContentInsets;
UIEdgeInsets _paddingAdjustments = new UIEdgeInsets();
public ButtonLayoutManager(IButtonLayoutRenderer renderer,
bool preserveInitialPadding = false,
bool spacingAdjustsPadding = true,
bool borderAdjustsPadding = false,
bool collapseHorizontalPadding = false)
{
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
_renderer.ElementChanged += OnElementChanged;
_preserveInitialPadding = preserveInitialPadding;
_spacingAdjustsPadding = spacingAdjustsPadding;
_borderAdjustsPadding = borderAdjustsPadding;
_collapseHorizontalPadding = collapseHorizontalPadding;
ImageElementManager.Init(renderer.ImageVisualElementRenderer);
}
UIButton Control => _renderer?.Control;
IImageVisualElementRenderer ImageVisualElementRenderer => _renderer?.ImageVisualElementRenderer;
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_renderer != null)
{
var imageRenderer = ImageVisualElementRenderer;
if (imageRenderer != null)
ImageElementManager.Dispose(imageRenderer);
_renderer.ElementChanged -= OnElementChanged;
_renderer = null;
}
}
_disposed = true;
}
}
public CGSize SizeThatFits(CGSize size, CGSize measured)
{
if (_disposed || _renderer == null || _element == null)
return measured;
var control = Control;
if (control == null)
return measured;
var minHeight = _renderer.MinimumHeight;
if (measured.Height < minHeight)
measured.Height = minHeight;
return measured;
}
public void Update()
{
UpdatePadding();
_ = UpdateImageAsync();
UpdateText();
UpdateEdgeInsets();
}
public void SetImage(UIImage image)
{
if (_disposed || _renderer == null || _element == null)
return;
var control = Control;
if (control == null)
return;
if (image != null)
{
control.SetImage(image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
control.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
}
else
{
control.SetImage(null, UIControlState.Normal);
}
UpdateEdgeInsets();
}
void OnElementChanged(object sender, ElementChangedEventArgs<Button> e)
{
if (_element != null)
{
_element.PropertyChanged -= OnElementPropertyChanged;
_element = null;
}
if (e.NewElement is Button button)
{
_element = button;
_element.PropertyChanged += OnElementPropertyChanged;
}
Update();
}
void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_disposed || _renderer == null || _element == null)
return;
if (e.PropertyName == Button.PaddingProperty.PropertyName)
UpdatePadding();
else if (e.PropertyName == Button.ImageProperty.PropertyName)
_ = UpdateImageAsync();
else if (e.PropertyName == Button.TextProperty.PropertyName)
UpdateText();
else if (e.PropertyName == Button.ContentLayoutProperty.PropertyName)
UpdateEdgeInsets();
else if (e.PropertyName == Button.BorderWidthProperty.PropertyName && _borderAdjustsPadding)
UpdateEdgeInsets();
}
void UpdateText()
{
if (_disposed || _renderer == null || _element == null)
return;
var control = Control;
if (control == null)
return;
control.SetTitle(_element.Text, UIControlState.Normal);
UpdateEdgeInsets();
}
async Task UpdateImageAsync()
{
if (_disposed || _renderer == null || _element == null)
return;
var imageRenderer = ImageVisualElementRenderer;
if (imageRenderer == null)
return;
try
{
await ImageElementManager.SetImage(imageRenderer, _element);
}
catch (Exception ex)
{
Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
}
}
void UpdatePadding()
{
if (_disposed || _renderer == null || _element == null)
return;
var control = Control;
if (control == null)
return;
EnsureDefaultInsets();
control.ContentEdgeInsets = GetPaddingInsets(_paddingAdjustments);
}
UIEdgeInsets GetPaddingInsets(UIEdgeInsets adjustments = default(UIEdgeInsets))
{
var defaultPadding = _preserveInitialPadding && _defaultContentInsets.HasValue
? _defaultContentInsets.Value
: new UIEdgeInsets();
return new UIEdgeInsets(
(nfloat)_element.Padding.Top + defaultPadding.Top + adjustments.Top,
(nfloat)_element.Padding.Left + defaultPadding.Left + adjustments.Left,
(nfloat)_element.Padding.Bottom + defaultPadding.Bottom + adjustments.Bottom,
(nfloat)_element.Padding.Right + defaultPadding.Right + adjustments.Right);
}
void EnsureDefaultInsets()
{
if (_disposed || _renderer == null || _element == null)
return;
var control = Control;
if (control == null)
return;
if (_defaultImageInsets == null)
_defaultImageInsets = control.ImageEdgeInsets;
if (_defaultTitleInsets == null)
_defaultTitleInsets = control.TitleEdgeInsets;
if (_defaultContentInsets == null)
_defaultContentInsets = control.ContentEdgeInsets;
}
void UpdateEdgeInsets()
{
if (_disposed || _renderer == null || _element == null)
return;
var control = Control;
if (control == null)
return;
EnsureDefaultInsets();
_paddingAdjustments = new UIEdgeInsets();
var imageInsets = new UIEdgeInsets();
var titleInsets = new UIEdgeInsets();
// adjust for the border
if (_borderAdjustsPadding && _element is IBorderElement borderElement && borderElement.IsBorderWidthSet() && borderElement.BorderWidth != borderElement.BorderWidthDefaultValue)
{
var width = (nfloat)_element.BorderWidth;
_paddingAdjustments.Top += width;
_paddingAdjustments.Bottom += width;
_paddingAdjustments.Left += width;
_paddingAdjustments.Right += width;
}
var layout = _element.ContentLayout;
var spacing = (nfloat)layout.Spacing;
var halfSpacing = spacing / 2;
var image = control.CurrentImage;
if (image != null && !string.IsNullOrEmpty(control.CurrentTitle))
{
// TODO: Do not use the title label as it is not yet updated and
// if we move the image, then we technically have more
// space and will require a new laoyt pass.
var titleRect = control.TitleLabel.Bounds.Size;
var titleWidth = titleRect.Width;
var titleHeight = titleRect.Height;
var imageWidth = image.Size.Width;
var imageHeight = image.Size.Height;
// adjust the padding for the spacing
if (layout.IsHorizontal())
{
var adjustment = _spacingAdjustsPadding ? halfSpacing * 2 : halfSpacing;
_paddingAdjustments.Left += adjustment;
_paddingAdjustments.Right += adjustment;
}
else
{
var adjustment = _spacingAdjustsPadding ? halfSpacing * 2 : halfSpacing;
_paddingAdjustments.Top += adjustment;
_paddingAdjustments.Bottom += adjustment;
}
// move the images according to the layout
if (layout.Position == Button.ButtonContentLayout.ImagePosition.Left)
{
// add a bit of spacing
imageInsets.Left -= halfSpacing;
imageInsets.Right += halfSpacing;
titleInsets.Left += halfSpacing;
titleInsets.Right -= halfSpacing;
}
else if (layout.Position == Button.ButtonContentLayout.ImagePosition.Right)
{
// swap the elements and add spacing
imageInsets.Left += titleWidth + halfSpacing;
imageInsets.Right -= titleWidth + halfSpacing;
titleInsets.Left -= imageWidth + halfSpacing;
titleInsets.Right += imageWidth + halfSpacing;
}
else
{
// we will move the image and title vertically
var imageVertical = (titleHeight / 2) + halfSpacing;
var titleVertical = (imageHeight / 2) + halfSpacing;
// the width will be different now that the image is no longer next to the text
nfloat horizontalAdjustment = 0;
if (_collapseHorizontalPadding)
horizontalAdjustment = (nfloat)(titleWidth + imageWidth - Math.Max(titleWidth, imageWidth)) / 2;
_paddingAdjustments.Left -= horizontalAdjustment;
_paddingAdjustments.Right -= horizontalAdjustment;
// the height will also be different
var verticalAdjustment = (nfloat)Math.Min(imageVertical, titleVertical);
_paddingAdjustments.Top += verticalAdjustment;
_paddingAdjustments.Bottom += verticalAdjustment;
// if the image is at the bottom, swap the direction
if (layout.Position == Button.ButtonContentLayout.ImagePosition.Bottom)
{
imageVertical = -imageVertical;
titleVertical = -titleVertical;
}
// move the image and title vertically
imageInsets.Top -= imageVertical;
imageInsets.Bottom += imageVertical;
titleInsets.Top += titleVertical;
titleInsets.Bottom -= titleVertical;
// center the elements horizontally
var imageHorizontal = titleWidth / 2;
var titleHorizontal = imageWidth / 2;
imageInsets.Left += imageHorizontal;
imageInsets.Right -= imageHorizontal;
titleInsets.Left -= titleHorizontal;
titleInsets.Right += titleHorizontal;
}
}
UpdatePadding();
control.ImageEdgeInsets = imageInsets;
control.TitleEdgeInsets = titleInsets;
}
}
}