MapBase dependency properties

This commit is contained in:
ClemensFischer 2024-05-21 13:51:10 +02:00
parent abe3bb75f9
commit 74f4e0176b
14 changed files with 1262 additions and 1037 deletions

View file

@ -14,41 +14,21 @@ namespace MapControl
{
internal static class Animatable
{
public static void BeginAnimation(this DependencyObject obj, string property, Timeline animation)
{
Storyboard.SetTargetProperty(animation, property);
Storyboard.SetTarget(animation, obj);
var storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
}
public static void BeginAnimation(this DependencyObject obj, DependencyProperty property, Timeline animation)
{
if (animation != null)
if (animation != null && property == UIElement.OpacityProperty)
{
string propertyName = null;
if (property == MapBase.CenterPointProperty)
{
propertyName = "CenterPoint";
((PointAnimation)animation).EnableDependentAnimation = true;
}
else if (property == MapBase.ZoomLevelProperty)
{
propertyName = "ZoomLevel";
((DoubleAnimation)animation).EnableDependentAnimation = true;
}
else if (property == MapBase.HeadingProperty)
{
propertyName = "Heading";
((DoubleAnimation)animation).EnableDependentAnimation = true;
}
else if (property == UIElement.OpacityProperty)
{
propertyName = "Opacity";
}
if (propertyName != null)
{
Storyboard.SetTargetProperty(animation, propertyName);
Storyboard.SetTarget(animation, obj);
var storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
}
BeginAnimation(obj, nameof(UIElement.Opacity), animation);
}
}
}

View file

@ -0,0 +1,44 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINUI
using Microsoft.UI.Xaml;
#else
using Windows.UI.Xaml;
#endif
namespace MapControl
{
public static class DependencyPropertyHelper
{
public static DependencyProperty Register<TOwner, TValue>(
string name,
TValue defaultValue = default,
bool bindTwoWayByDefault = false,
Action<TOwner, TValue, TValue> changed = null)
where TOwner : DependencyObject
{
var metadata = changed != null
? new PropertyMetadata(defaultValue, (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue))
: new PropertyMetadata(defaultValue);
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyProperty RegisterAttached<TOwner, TValue>(
string name,
TValue defaultValue = default,
bool inherits = false,
Action<FrameworkElement, TValue, TValue> changed = null)
where TOwner : DependencyObject
{
var metadata = changed != null
? new PropertyMetadata(defaultValue, (o, e) => changed((FrameworkElement)o, (TValue)e.OldValue, (TValue)e.NewValue))
: new PropertyMetadata(defaultValue);
return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata);
}
}
}

View file

@ -6,50 +6,60 @@
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
#else
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
#endif
namespace MapControl
{
public partial class MapBase
{
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
nameof(Center), typeof(Location), typeof(MapBase),
new PropertyMetadata(new Location(), (o, e) => ((MapBase)o).CenterPropertyChanged((Location)e.NewValue)));
public static readonly DependencyProperty AnimationEasingFunctionProperty =
DependencyPropertyHelper.Register<MapBase, EasingFunctionBase>(nameof(AnimationEasingFunction),
new QuadraticEase { EasingMode = EasingMode.EaseOut });
public static readonly DependencyProperty TargetCenterProperty = DependencyProperty.Register(
nameof(TargetCenter), typeof(Location), typeof(MapBase),
new PropertyMetadata(new Location(), (o, e) => ((MapBase)o).TargetCenterPropertyChanged((Location)e.NewValue)));
public static readonly DependencyProperty CenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(Center), new Location(), true,
(map, oldValue, newValue) => map.CenterPropertyChanged(newValue));
public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register(
nameof(ZoomLevel), typeof(double), typeof(MapBase),
new PropertyMetadata(1d, (o, e) => ((MapBase)o).ZoomLevelPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty TargetCenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(TargetCenter), new Location(), true,
(map, oldValue, newValue) => map.TargetCenterPropertyChanged(newValue));
public static readonly DependencyProperty TargetZoomLevelProperty = DependencyProperty.Register(
nameof(TargetZoomLevel), typeof(double), typeof(MapBase),
new PropertyMetadata(1d, (o, e) => ((MapBase)o).TargetZoomLevelPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty MinZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MinZoomLevel), 1d, false,
(map, oldValue, newValue) => map.MinZoomLevelPropertyChanged(newValue));
public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register(
nameof(Heading), typeof(double), typeof(MapBase),
new PropertyMetadata(0d, (o, e) => ((MapBase)o).HeadingPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty MaxZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MaxZoomLevel), 20d, false,
(map, oldValue, newValue) => map.MaxZoomLevelPropertyChanged(newValue));
public static readonly DependencyProperty TargetHeadingProperty = DependencyProperty.Register(
nameof(TargetHeading), typeof(double), typeof(MapBase),
new PropertyMetadata(0d, (o, e) => ((MapBase)o).TargetHeadingPropertyChanged((double)e.NewValue)));
public static readonly DependencyProperty ZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(ZoomLevel), 1d, true,
(map, oldValue, newValue) => map.ZoomLevelPropertyChanged(newValue));
public static readonly DependencyProperty ViewScaleProperty = DependencyProperty.Register(
nameof(ViewScale), typeof(double), typeof(MapBase), new PropertyMetadata(0d));
public static readonly DependencyProperty TargetZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetZoomLevel), 1d, true,
(map, oldValue, newValue) => map.TargetZoomLevelPropertyChanged(newValue));
internal static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
"CenterPoint", typeof(Windows.Foundation.Point), typeof(MapBase),
new PropertyMetadata(new Windows.Foundation.Point(), (o, e) =>
{
var center = (Windows.Foundation.Point)e.NewValue;
((MapBase)o).CenterPointPropertyChanged(new Location(center.Y, center.X));
}));
public static readonly DependencyProperty HeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d, true,
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue));
public static readonly DependencyProperty TargetHeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d, true,
(map, oldValue, newValue) => map.TargetHeadingPropertyChanged(newValue));
public static readonly DependencyProperty ViewScaleProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(ViewScale), 0d);
private PointAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
public MapBase()
{
@ -62,6 +72,36 @@ namespace MapControl
SizeChanged += OnSizeChanged;
}
/// <summary>
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
/// The default value is a QuadraticEase with EasingMode.EaseOut.
/// </summary>
public EasingFunctionBase AnimationEasingFunction
{
get => (EasingFunctionBase)GetValue(AnimationEasingFunctionProperty);
set => SetValue(AnimationEasingFunctionProperty, value);
}
/// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter.
/// </summary>
public double ViewScale => (double)GetValue(ViewScaleProperty);
/// <summary>
/// Gets a transform Matrix for scaling and rotating objects that are anchored
/// at a Location from map coordinates (i.e. meters) to view coordinates.
/// </summary>
public Matrix GetMapTransform(Location location)
{
var scale = GetScale(location);
var transform = new Matrix(scale.X, 0d, 0d, scale.Y, 0d, 0d);
transform.Rotate(ViewTransform.Rotation);
return transform;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
Clip = new RectangleGeometry
@ -77,5 +117,248 @@ namespace MapControl
{
SetValue(ViewScaleProperty, scale);
}
private void CenterPropertyChanged(Location value)
{
if (!internalPropertyChange)
{
var center = CoerceCenterProperty(value);
if (!center.Equals(value))
{
SetValueInternal(CenterProperty, center);
}
UpdateTransform();
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
}
}
}
#pragma warning disable IDE0052 // Remove unread private members
private static readonly DependencyProperty CenterPointProperty =
DependencyPropertyHelper.Register<MapBase, Windows.Foundation.Point>("CenterPoint",
new Windows.Foundation.Point(), false, (map, oldValue, newValue) => map.Center = new Location(newValue.Y, newValue.X));
#pragma warning restore IDE0052
private void TargetCenterPropertyChanged(Location value)
{
if (!internalPropertyChange)
{
var targetCenter = CoerceCenterProperty(value);
if (!targetCenter.Equals(value))
{
SetValueInternal(TargetCenterProperty, targetCenter);
}
if (!targetCenter.Equals(Center))
{
if (centerAnimation != null)
{
centerAnimation.Completed -= CenterAnimationCompleted;
}
centerAnimation = new PointAnimation
{
From = new Windows.Foundation.Point(Center.Longitude, Center.Latitude),
To = new Windows.Foundation.Point(ConstrainedLongitude(targetCenter.Longitude), targetCenter.Latitude),
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
EnableDependentAnimation = true
};
centerAnimation.Completed += CenterAnimationCompleted;
this.BeginAnimation("CenterPoint", centerAnimation);
}
}
}
private void CenterAnimationCompleted(object sender, object e)
{
if (centerAnimation != null)
{
SetValueInternal(CenterProperty, TargetCenter);
UpdateTransform();
centerAnimation.Completed -= CenterAnimationCompleted;
centerAnimation = null;
}
}
private void MinZoomLevelPropertyChanged(double value)
{
var minZoomLevel = CoerceMinZoomLevelProperty(value);
if (minZoomLevel != value)
{
SetValueInternal(MinZoomLevelProperty, minZoomLevel);
}
if (ZoomLevel < minZoomLevel)
{
ZoomLevel = minZoomLevel;
}
}
private void MaxZoomLevelPropertyChanged(double value)
{
var maxZoomLevel = CoerceMaxZoomLevelProperty(value);
if (maxZoomLevel != value)
{
SetValueInternal(MaxZoomLevelProperty, maxZoomLevel);
}
if (ZoomLevel > maxZoomLevel)
{
ZoomLevel = maxZoomLevel;
}
}
private void ZoomLevelPropertyChanged(double value)
{
if (!internalPropertyChange)
{
var zoomLevel = CoerceZoomLevelProperty(value);
if (zoomLevel != value)
{
SetValueInternal(ZoomLevelProperty, zoomLevel);
}
UpdateTransform();
if (zoomLevelAnimation == null)
{
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
}
}
}
private void TargetZoomLevelPropertyChanged(double value)
{
if (!internalPropertyChange)
{
var targetZoomLevel = CoerceZoomLevelProperty(value);
if (targetZoomLevel != value)
{
SetValueInternal(TargetZoomLevelProperty, targetZoomLevel);
}
if (targetZoomLevel != ZoomLevel)
{
if (zoomLevelAnimation != null)
{
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
}
zoomLevelAnimation = new DoubleAnimation
{
To = targetZoomLevel,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
EnableDependentAnimation = true
};
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
this.BeginAnimation(nameof(ZoomLevel), zoomLevelAnimation);
}
}
}
private void ZoomLevelAnimationCompleted(object sender, object e)
{
if (zoomLevelAnimation != null)
{
SetValueInternal(ZoomLevelProperty, TargetZoomLevel);
UpdateTransform(true);
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
zoomLevelAnimation = null;
}
}
private void HeadingPropertyChanged(double value)
{
if (!internalPropertyChange)
{
var heading = CoerceHeadingProperty(value);
if (heading != value)
{
SetValueInternal(HeadingProperty, heading);
}
UpdateTransform();
if (headingAnimation == null)
{
SetValueInternal(TargetHeadingProperty, heading);
}
}
}
private void TargetHeadingPropertyChanged(double value)
{
if (!internalPropertyChange)
{
var targetHeading = CoerceHeadingProperty(value);
if (targetHeading != value)
{
SetValueInternal(TargetHeadingProperty, targetHeading);
}
if (targetHeading != Heading)
{
var delta = targetHeading - Heading;
if (delta > 180d)
{
delta -= 360d;
}
else if (delta < -180d)
{
delta += 360d;
}
if (headingAnimation != null)
{
headingAnimation.Completed -= HeadingAnimationCompleted;
}
headingAnimation = new DoubleAnimation
{
By = delta,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
EnableDependentAnimation = true
};
headingAnimation.Completed += HeadingAnimationCompleted;
this.BeginAnimation(nameof(Heading), headingAnimation);
}
}
}
private void HeadingAnimationCompleted(object sender, object e)
{
if (headingAnimation != null)
{
SetValueInternal(HeadingProperty, TargetHeading);
UpdateTransform();
headingAnimation.Completed -= HeadingAnimationCompleted;
headingAnimation = null;
}
}
}
}