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

@ -0,0 +1,68 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
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,
Func<TOwner, TValue, TValue> coerce = null)
where TOwner : DependencyObject
{
var metadata = new FrameworkPropertyMetadata
{
DefaultValue = defaultValue,
BindsTwoWayByDefault = bindTwoWayByDefault
};
if (changed != null)
{
metadata.PropertyChangedCallback = (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue);
}
if (coerce != null)
{
metadata.CoerceValueCallback = (o, v) => coerce((TOwner)o, (TValue)v);
}
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 = new FrameworkPropertyMetadata
{
DefaultValue = defaultValue,
Inherits = inherits
};
if (changed != null)
{
metadata.PropertyChangedCallback = (o, e) => changed((FrameworkElement)o, (TValue)e.OldValue, (TValue)e.NewValue);
}
return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyPropertyKey RegisterReadOnly<TOwner, TValue>(
string name,
TValue defaultValue = default)
where TOwner : DependencyObject
{
return DependencyProperty.RegisterReadOnly(name, typeof(TValue), typeof(TOwner), new PropertyMetadata(defaultValue));
}
}
}

View file

@ -0,0 +1,43 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media.Animation;
namespace MapControl
{
public class LocationAnimation : AnimationTimeline
{
public override Type TargetPropertyType => typeof(Location);
public Location To { get; set; }
public IEasingFunction EasingFunction { get; set; }
protected override Freezable CreateInstanceCore()
{
return new LocationAnimation
{
To = To,
EasingFunction = EasingFunction
};
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
var from = (Location)defaultOriginValue;
var progress = animationClock.CurrentProgress ?? 1d;
if (EasingFunction != null)
{
progress = EasingFunction.Ease(progress);
}
return new Location(
(1d - progress) * from.Latitude + progress * To.Latitude,
(1d - progress) * from.Longitude + progress * To.Longitude);
}
}
}

View file

@ -3,53 +3,65 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace MapControl
{
public partial class MapBase
{
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
nameof(Center), typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata(
new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((MapBase)o).CenterPropertyChanged((Location)e.NewValue)));
public static readonly DependencyProperty AnimationEasingFunctionProperty =
DependencyPropertyHelper.Register<MapBase, IEasingFunction>(nameof(AnimationEasingFunction),
new QuadraticEase { EasingMode = EasingMode.EaseOut });
public static readonly DependencyProperty TargetCenterProperty = DependencyProperty.Register(
nameof(TargetCenter), typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata(
new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(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),
(map, value) => map.CoerceCenterProperty(value));
public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register(
nameof(ZoomLevel), typeof(double), typeof(MapBase), new FrameworkPropertyMetadata(
1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(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),
(map, value) => map.CoerceCenterProperty(value));
public static readonly DependencyProperty TargetZoomLevelProperty = DependencyProperty.Register(
nameof(TargetZoomLevel), typeof(double), typeof(MapBase), new FrameworkPropertyMetadata(
1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(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),
(map, value) => map.CoerceMinZoomLevelProperty(value));
public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register(
nameof(Heading), typeof(double), typeof(MapBase), new FrameworkPropertyMetadata(
0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(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),
(map, value) => map.CoerceMinZoomLevelProperty(value));
public static readonly DependencyProperty TargetHeadingProperty = DependencyProperty.Register(
nameof(TargetHeading), typeof(double), typeof(MapBase), new FrameworkPropertyMetadata(
0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(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),
(map, value) => map.CoerceZoomLevelProperty(value));
private static readonly DependencyPropertyKey ViewScalePropertyKey = DependencyProperty.RegisterReadOnly(
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),
(map, value) => map.CoerceZoomLevelProperty(value));
public static readonly DependencyProperty HeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d, true,
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value));
public static readonly DependencyProperty TargetHeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d, true,
(map, oldValue, newValue) => map.TargetHeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value));
private static readonly DependencyPropertyKey ViewScalePropertyKey =
DependencyPropertyHelper.RegisterReadOnly<MapBase, double>(nameof(ViewScale), 0d);
public static readonly DependencyProperty ViewScaleProperty = ViewScalePropertyKey.DependencyProperty;
private static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
"CenterPoint", typeof(Point), typeof(MapBase), new PropertyMetadata(new Point(),
(o, e) =>
{
var center = (Point)e.NewValue;
((MapBase)o).CenterPointPropertyChanged(new Location(center.Y, center.X));
}));
private LocationAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
static MapBase()
{
@ -57,6 +69,36 @@ namespace MapControl
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(typeof(MapBase)));
}
/// <summary>
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
/// The default value is a QuadraticEase with EasingMode.EaseOut.
/// </summary>
public IEasingFunction AnimationEasingFunction
{
get => (IEasingFunction)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;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
@ -69,5 +111,179 @@ namespace MapControl
{
SetValue(ViewScalePropertyKey, scale);
}
private void CenterPropertyChanged(Location center)
{
if (!internalPropertyChange)
{
UpdateTransform();
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
}
}
}
private void TargetCenterPropertyChanged(Location targetCenter)
{
if (!internalPropertyChange && !targetCenter.Equals(Center))
{
if (centerAnimation != null)
{
centerAnimation.Completed -= CenterAnimationCompleted;
}
centerAnimation = new LocationAnimation
{
To = new Location(targetCenter.Latitude, ConstrainedLongitude(targetCenter.Longitude)),
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
centerAnimation.Completed += CenterAnimationCompleted;
BeginAnimation(CenterProperty, centerAnimation);
}
}
private void CenterAnimationCompleted(object sender, object e)
{
if (centerAnimation != null)
{
SetValueInternal(CenterProperty, TargetCenter);
UpdateTransform();
centerAnimation.Completed -= CenterAnimationCompleted;
centerAnimation = null;
BeginAnimation(CenterProperty, null);
}
}
private void MinZoomLevelPropertyChanged(double minZoomLevel)
{
if (ZoomLevel < minZoomLevel)
{
ZoomLevel = minZoomLevel;
}
}
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
{
if (ZoomLevel > maxZoomLevel)
{
ZoomLevel = maxZoomLevel;
}
}
private void ZoomLevelPropertyChanged(double zoomLevel)
{
if (!internalPropertyChange)
{
UpdateTransform();
if (zoomLevelAnimation == null)
{
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
}
}
}
private void TargetZoomLevelPropertyChanged(double targetZoomLevel)
{
if (!internalPropertyChange && targetZoomLevel != ZoomLevel)
{
if (zoomLevelAnimation != null)
{
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
}
zoomLevelAnimation = new DoubleAnimation
{
To = targetZoomLevel,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
BeginAnimation(ZoomLevelProperty, zoomLevelAnimation);
}
}
private void ZoomLevelAnimationCompleted(object sender, object e)
{
if (zoomLevelAnimation != null)
{
SetValueInternal(ZoomLevelProperty, TargetZoomLevel);
UpdateTransform(true);
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
zoomLevelAnimation = null;
BeginAnimation(ZoomLevelProperty, null);
}
}
private void HeadingPropertyChanged(double heading)
{
if (!internalPropertyChange)
{
UpdateTransform();
if (headingAnimation == null)
{
SetValueInternal(TargetHeadingProperty, heading);
}
}
}
private void TargetHeadingPropertyChanged(double targetHeading)
{
if (!internalPropertyChange && 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
};
headingAnimation.Completed += HeadingAnimationCompleted;
BeginAnimation(HeadingProperty, headingAnimation);
}
}
private void HeadingAnimationCompleted(object sender, object e)
{
if (headingAnimation != null)
{
SetValueInternal(HeadingProperty, TargetHeading);
UpdateTransform();
headingAnimation.Completed -= HeadingAnimationCompleted;
headingAnimation = null;
BeginAnimation(HeadingProperty, null);
}
}
}
}