File: ImageElement.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls
{
	static class ImageElement
	{
		/// <summary>Bindable property for <c>ImageSource</c>.</summary>
		public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create("ImageSource", typeof(ImageSource), typeof(IImageElement), default(ImageSource),
			propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
 
		/// <summary>Bindable property for <see cref="IImageElement.Source"/>.</summary>
		public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(IImageElement.Source), typeof(ImageSource), typeof(IImageElement), default(ImageSource),
			propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
 
		/// <summary>Bindable property for <see cref="IImageElement.Aspect"/>.</summary>
		public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(IImageElement.Aspect), typeof(Aspect), typeof(IImageElement), Aspect.AspectFit);
 
		/// <summary>Bindable property for <see cref="IImageElement.IsOpaque"/>.</summary>
		public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create(nameof(IImageElement.IsOpaque), typeof(bool), typeof(IImageElement), false);
 
		internal static readonly BindableProperty IsAnimationPlayingProperty = BindableProperty.Create(nameof(IImageElement.IsAnimationPlaying), typeof(bool), typeof(IImageElement), false);
 
		static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var newSource = (ImageSource)newValue;
			var image = (IImageElement)bindable;
			if (newSource != null && image != null)
				newSource.SourceChanged += image.OnImageSourceSourceChanged;
			ImageSourceChanged(bindable, newSource);
		}
 
		static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var oldSource = (ImageSource)oldValue;
			var image = (IImageElement)bindable;
 
			if (oldSource != null && image != null)
				oldSource.SourceChanged -= image.OnImageSourceSourceChanged;
			ImageSourceChanging(oldSource);
		}
 
#pragma warning disable CS0618 // Type or member is obsolete
		public static SizeRequest Measure(IImageElement 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));
		}
#pragma warning restore CS0618 // Type or member is obsolete
 
		public static void OnBindingContextChanged(IImageElement image, VisualElement visualElement)
		{
			if (image.Source != null)
				BindableObject.SetInheritedBindingContext(image.Source, visualElement?.BindingContext);
		}
 
 
		public static void ImageSourceChanging(ImageSource oldImageSource)
		{
			if (oldImageSource == null)
				return;
			try
			{
				// This method generates are particularly unhappy async/await state-machine combined
				// with the CoW mechanisms of the try/catch that actually makes it worth avoiding
				// if oldValue is null (which it often is). Early return does not prevent the
				// state-machine mechanisms being kicked in, only moving to another method will do that.
				CancelOldValue(oldImageSource);
			}
			catch (ObjectDisposedException)
			{
				// Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
			}
		}
 
		async static void CancelOldValue(ImageSource oldvalue)
		{
			try
			{
				await oldvalue.Cancel();
			}
			catch (ObjectDisposedException)
			{
				// Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
			}
		}
 
		static void ImageSourceChanged(BindableObject bindable, ImageSource newSource)
		{
			var imageElement = (VisualElement)bindable;
			if (newSource != null)
				newSource.Parent = imageElement;
			imageElement?.InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
		}
 
		public static void ImageSourceSourceChanged(object sender, EventArgs e)
		{
			if (sender is IImageElement imageController)
				imageController.RaiseImageSourcePropertyChanged();
 
			((VisualElement)sender).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
		}
 
 
		internal static bool GetLoadAsAnimation(BindableObject bindable)
		{
			return bindable.IsSet(IsAnimationPlayingProperty);
		}
	}
}