XAML-Map-Control/MapControl/Avalonia/MapBase.Avalonia.cs

274 lines
8.9 KiB
C#
Raw Normal View History

2025-11-13 13:36:28 +01:00
using Avalonia;
2025-08-19 19:43:02 +02:00
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Media;
using Avalonia.Styling;
2024-05-19 23:23:27 +02:00
using System.Threading;
using System.Threading.Tasks;
2025-11-13 13:36:28 +01:00
using Brush = Avalonia.Media.IBrush;
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
namespace MapControl;
public partial class MapBase
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
public static readonly StyledProperty<Brush> ForegroundProperty =
DependencyPropertyHelper.AddOwner<MapBase, Brush>(TextElement.ForegroundProperty, Brushes.Black);
public static readonly StyledProperty<Easing> AnimationEasingProperty =
DependencyPropertyHelper.Register<MapBase, Easing>(nameof(AnimationEasing), new QuadraticEaseOut());
public static readonly StyledProperty<Location> CenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(Center), new Location(0d, 0d),
(map, oldValue, newValue) => map.CenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
public static readonly StyledProperty<Location> TargetCenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(TargetCenter), new Location(0d, 0d),
async (map, oldValue, newValue) => await map.TargetCenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
public static readonly StyledProperty<double> MinZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MinZoomLevel), 1d,
(map, oldValue, newValue) => map.MinZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMinZoomLevelProperty(value));
public static readonly StyledProperty<double> MaxZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MaxZoomLevel), 20d,
(map, oldValue, newValue) => map.MaxZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMaxZoomLevelProperty(value));
public static readonly StyledProperty<double> ZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(ZoomLevel), 1d,
(map, oldValue, newValue) => map.ZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly StyledProperty<double> TargetZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetZoomLevel), 1d,
async (map, oldValue, newValue) => await map.TargetZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly StyledProperty<double> HeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d,
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
public static readonly StyledProperty<double> TargetHeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d,
async (map, oldValue, newValue) => await map.TargetHeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
public static readonly DirectProperty<MapBase, double> ViewScaleProperty =
AvaloniaProperty.RegisterDirect<MapBase, double>(nameof(ViewScale), map => map.ViewTransform.Scale);
private CancellationTokenSource centerCts;
private CancellationTokenSource zoomLevelCts;
private CancellationTokenSource headingCts;
private Animation centerAnimation;
private Animation zoomLevelAnimation;
private Animation headingAnimation;
static MapBase()
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
BackgroundProperty.OverrideDefaultValue<MapBase>(Brushes.White);
ClipToBoundsProperty.OverrideDefaultValue<MapBase>(true);
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
Animation.RegisterCustomAnimator<Location, LocationAnimator>();
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
public double ActualWidth => Bounds.Width;
public double ActualHeight => Bounds.Height;
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
protected override void OnSizeChanged(SizeChangedEventArgs e)
{
base.OnSizeChanged(e);
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
ResetTransformCenter();
UpdateTransform();
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
/// <summary>
/// Gets or sets the Easing of the Center, ZoomLevel and Heading animations.
/// The default value is a QuadraticEaseOut.
/// </summary>
public Easing AnimationEasing
{
get => GetValue(AnimationEasingProperty);
set => SetValue(AnimationEasingProperty, value);
}
2024-05-21 14:48:03 +02:00
2026-04-13 17:14:49 +02:00
/// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter.
/// </summary>
public double ViewScale
{
get => GetValue(ViewScaleProperty);
private set => RaisePropertyChanged(ViewScaleProperty, double.NaN, value);
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private void CenterPropertyChanged(Location center)
{
if (!internalPropertyChange)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
UpdateTransform();
2024-05-21 00:00:30 +02:00
2026-04-13 17:14:49 +02:00
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
2024-05-21 00:00:30 +02:00
}
}
2026-04-13 17:14:49 +02:00
}
2024-05-21 00:00:30 +02:00
2026-04-13 17:14:49 +02:00
private async Task TargetCenterPropertyChanged(Location targetCenter)
{
if (!internalPropertyChange && !targetCenter.Equals(Center))
2024-05-21 00:00:30 +02:00
{
2026-04-13 17:14:49 +02:00
ResetTransformCenter();
2026-04-13 17:14:49 +02:00
centerCts?.Cancel();
2024-05-21 00:00:30 +02:00
2026-04-13 17:14:49 +02:00
centerAnimation = CreateAnimation(CenterProperty, new Location(targetCenter.Latitude, NearestLongitude(targetCenter.Longitude)));
2024-05-21 00:00:30 +02:00
2026-04-13 17:14:49 +02:00
using (centerCts = new CancellationTokenSource())
{
await centerAnimation.RunAsync(this, centerCts.Token);
2025-07-06 19:43:10 +02:00
2026-04-13 17:14:49 +02:00
if (!centerCts.IsCancellationRequested)
{
UpdateTransform();
}
2024-05-19 23:23:27 +02:00
}
2026-04-13 17:14:49 +02:00
centerCts = null;
centerAnimation = null;
2024-05-20 23:24:34 +02:00
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private void MinZoomLevelPropertyChanged(double minZoomLevel)
{
if (ZoomLevel < minZoomLevel)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
ZoomLevel = minZoomLevel;
2024-05-20 23:24:34 +02:00
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
{
if (ZoomLevel > maxZoomLevel)
2024-05-20 23:24:34 +02:00
{
2026-04-13 17:14:49 +02:00
ZoomLevel = maxZoomLevel;
2024-05-20 23:24:34 +02:00
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private void ZoomLevelPropertyChanged(double zoomLevel)
{
if (!internalPropertyChange)
2024-05-20 23:24:34 +02:00
{
2026-04-13 17:14:49 +02:00
UpdateTransform();
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
if (zoomLevelAnimation == null)
{
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
2024-05-19 23:23:27 +02:00
}
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private async Task TargetZoomLevelPropertyChanged(double targetZoomLevel)
{
if (!internalPropertyChange && targetZoomLevel != ZoomLevel)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
zoomLevelCts?.Cancel();
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
zoomLevelAnimation = CreateAnimation(ZoomLevelProperty, targetZoomLevel);
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
using (zoomLevelCts = new CancellationTokenSource())
{
await zoomLevelAnimation.RunAsync(this, zoomLevelCts.Token);
2025-07-06 19:43:10 +02:00
2026-04-13 17:14:49 +02:00
if (!zoomLevelCts.IsCancellationRequested)
{
UpdateTransform(true); // reset transform center
2024-05-21 13:51:10 +02:00
}
2024-05-20 23:24:34 +02:00
}
2026-04-13 17:14:49 +02:00
zoomLevelCts = null;
zoomLevelAnimation = null;
2024-05-19 23:23:27 +02:00
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private void HeadingPropertyChanged(double heading)
{
if (!internalPropertyChange)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
UpdateTransform();
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
if (headingAnimation == null)
{
SetValueInternal(TargetHeadingProperty, heading);
2024-05-19 23:23:27 +02:00
}
}
2026-04-13 17:14:49 +02:00
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
private async Task TargetHeadingPropertyChanged(double targetHeading)
{
if (!internalPropertyChange && targetHeading != Heading)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
var delta = targetHeading - Heading;
if (delta > 180d)
2024-05-19 23:23:27 +02:00
{
2026-04-13 17:14:49 +02:00
delta -= 360d;
}
else if (delta < -180d)
{
delta += 360d;
}
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
targetHeading = Heading + delta;
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
headingCts?.Cancel();
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
headingAnimation = CreateAnimation(HeadingProperty, targetHeading);
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
using (headingCts = new CancellationTokenSource())
{
await headingAnimation.RunAsync(this, headingCts.Token);
2024-05-19 23:23:27 +02:00
2026-04-13 17:14:49 +02:00
if (!headingCts.IsCancellationRequested)
{
2026-04-13 17:14:49 +02:00
UpdateTransform();
}
2024-05-19 23:23:27 +02:00
}
2026-04-13 17:14:49 +02:00
headingCts = null;
headingAnimation = null;
2024-05-19 23:23:27 +02:00
}
2026-04-13 17:14:49 +02:00
}
2025-01-05 09:22:50 +01:00
2026-04-13 17:14:49 +02:00
private Animation CreateAnimation(DependencyProperty property, object value)
{
return new Animation
2025-01-05 09:22:50 +01:00
{
2026-04-13 17:14:49 +02:00
FillMode = FillMode.Forward,
Duration = AnimationDuration,
Easing = AnimationEasing,
Children =
{
new KeyFrame
2025-01-05 09:22:50 +01:00
{
2026-04-13 17:14:49 +02:00
KeyTime = AnimationDuration,
Setters = { new Setter(property, value) }
2025-01-05 09:22:50 +01:00
}
2026-04-13 17:14:49 +02:00
}
};
2024-05-19 23:23:27 +02:00
}
}