diff --git a/MapControl/Avalonia/Map.Avalonia.cs b/MapControl/Avalonia/Map.Avalonia.cs index 18fe7b3e..38513751 100644 --- a/MapControl/Avalonia/Map.Avalonia.cs +++ b/MapControl/Avalonia/Map.Avalonia.cs @@ -62,11 +62,24 @@ namespace MapControl base.OnPointerWheelChanged(e); } - protected override void OnPointerPressed(PointerPressedEventArgs e) + protected override void OnPointerMoved(PointerEventArgs e) { var point = e.GetCurrentPoint(this); - if (pointer1 == null && point.Pointer.Type == PointerType.Mouse && point.Properties.IsLeftButtonPressed || + if (point.Pointer == pointer1 || point.Pointer == pointer2) + { + if (pointer2 != null) + { + HandleManipulation(point.Pointer, point.Position); + } + else if (point.Pointer.Type == PointerType.Mouse || ManipulationModes.HasFlag(ManipulationModes.Translate)) + { + TranslateMap(new Point(point.Position.X - position1.X, point.Position.Y - position1.Y)); + position1 = point.Position; + } + } + else if ( + pointer1 == null && point.Pointer.Type == PointerType.Mouse && point.Properties.IsLeftButtonPressed && e.KeyModifiers == KeyModifiers.None || pointer2 == null && point.Pointer.Type == PointerType.Touch && ManipulationModes != ManipulationModes.None) { point.Pointer.Capture(this); @@ -83,7 +96,7 @@ namespace MapControl } } - base.OnPointerPressed(e); + base.OnPointerMoved(e); } protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) @@ -102,26 +115,6 @@ namespace MapControl base.OnPointerCaptureLost(e); } - protected override void OnPointerMoved(PointerEventArgs e) - { - if (e.Pointer == pointer1 || e.Pointer == pointer2) - { - var position = e.GetPosition(this); - - if (pointer2 != null) - { - HandleManipulation(e.Pointer, position); - } - else if (e.Pointer.Type == PointerType.Mouse || ManipulationModes.HasFlag(ManipulationModes.Translate)) - { - TranslateMap(new Point(position.X - position1.X, position.Y - position1.Y)); - position1 = position; - } - } - - base.OnPointerMoved(e); - } - private void HandleManipulation(IPointer pointer, Point position) { var oldDistance = new Vector(position2.X - position1.X, position2.Y - position1.Y); diff --git a/MapControl/Avalonia/MapItem.Avalonia.cs b/MapControl/Avalonia/MapItem.Avalonia.cs index e6c5d1b8..29addcd3 100644 --- a/MapControl/Avalonia/MapItem.Avalonia.cs +++ b/MapControl/Avalonia/MapItem.Avalonia.cs @@ -11,14 +11,28 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + if (e.Pointer.Type == PointerType.Touch) { - e.Handled = true; - MapItemsControl.SelectItemsInRange(this); + UpdateSelection(e); } - else + + e.Handled = true; + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + UpdateSelection(e); + + e.Handled = true; + } + + private void UpdateSelection(PointerEventArgs e) + { + if (ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl) { - base.OnPointerPressed(e); + mapItemsControl.UpdateSelection(this, + e.KeyModifiers.HasFlag(KeyModifiers.Control), + e.KeyModifiers.HasFlag(KeyModifiers.Shift)); } } } diff --git a/MapControl/Avalonia/MapItemsControl.Avalonia.cs b/MapControl/Avalonia/MapItemsControl.Avalonia.cs index 34d05182..58f053a2 100644 --- a/MapControl/Avalonia/MapItemsControl.Avalonia.cs +++ b/MapControl/Avalonia/MapItemsControl.Avalonia.cs @@ -56,5 +56,50 @@ namespace MapControl mapItem.ClearValue(MapItem.LocationProperty); } } + + internal void UpdateSelection(MapItem mapItem, bool controlKeyPressed, bool shiftKeyPressed) + { + if (SelectionMode != SelectionMode.Single && shiftKeyPressed) + { + SelectItemsInRange(mapItem); + } + else + { + UpdateSelection(mapItem, true, false, controlKeyPressed); + } + + //var item = ItemFromContainer(mapItem); + + //if (SelectionMode == SelectionMode.Single) + //{ + // if (SelectedItem != item) + // { + // SelectedItem = item; + // } + // else if (controlKeyPressed) + // { + // SelectedItem = null; + // } + //} + //else if (controlKeyPressed) + //{ + // if (SelectedItems.Contains(item)) + // { + // SelectedItems.Remove(item); + // } + // else + // { + // SelectedItems.Add(item); + // } + //} + //else if (shiftKeyPressed) + //{ + // SelectItemsInRange(mapItem); + //} + //else if (SelectedItem != item || SelectedItems.Count != 1) + //{ + // SelectedItem = item; + //} + } } } diff --git a/MapControl/Shared/MapItemsControl.cs b/MapControl/Shared/MapItemsControl.cs index 424fb34a..df3d0f82 100644 --- a/MapControl/Shared/MapItemsControl.cs +++ b/MapControl/Shared/MapItemsControl.cs @@ -68,9 +68,9 @@ namespace MapControl { SelectItems(item => { - var pos = MapPanel.GetViewPosition(ContainerFromItem(item)); + var position = MapPanel.GetViewPosition(ContainerFromItem(item)); - return pos.HasValue && predicate(pos.Value); + return position.HasValue && predicate(position.Value); }); } @@ -82,40 +82,36 @@ namespace MapControl /// /// Selects all items in a rectangular range between SelectedItem and the specified MapItem. /// - internal static void SelectItemsInRange(MapItem mapItem) + internal void SelectItemsInRange(MapItem mapItem) { - if (ItemsControlFromItemContainer(mapItem) is MapItemsControl mapItemsControl && - mapItemsControl.SelectionMode != SelectionMode.Single) + var position = MapPanel.GetViewPosition(mapItem); + + if (position.HasValue) { - var pos = MapPanel.GetViewPosition(mapItem); + var xMin = position.Value.X; + var xMax = position.Value.X; + var yMin = position.Value.Y; + var yMax = position.Value.Y; - if (pos.HasValue) + if (SelectedItem != null) { - var xMin = pos.Value.X; - var xMax = pos.Value.X; - var yMin = pos.Value.Y; - var yMax = pos.Value.Y; + var selectedMapItem = ContainerFromItem(SelectedItem); - if (mapItemsControl.SelectedItem != null) + if (selectedMapItem != mapItem) { - var selectedMapItem = mapItemsControl.ContainerFromItem(mapItemsControl.SelectedItem); + position = MapPanel.GetViewPosition(selectedMapItem); - if (selectedMapItem != mapItem) + if (position.HasValue) { - pos = MapPanel.GetViewPosition(selectedMapItem); - - if (pos.HasValue) - { - xMin = Math.Min(xMin, pos.Value.X); - xMax = Math.Max(xMax, pos.Value.X); - yMin = Math.Min(yMin, pos.Value.Y); - yMax = Math.Max(yMax, pos.Value.Y); - } + xMin = Math.Min(xMin, position.Value.X); + xMax = Math.Max(xMax, position.Value.X); + yMin = Math.Min(yMin, position.Value.Y); + yMax = Math.Max(yMax, position.Value.Y); } } - - mapItemsControl.SelectItemsInRect(new Rect(xMin, yMin, xMax - xMin, yMax - yMin)); } + + SelectItemsInRect(new Rect(xMin, yMin, xMax - xMin, yMax - yMin)); } } } diff --git a/MapControl/WPF/Map.WPF.cs b/MapControl/WPF/Map.WPF.cs index eaa509cc..97bfc564 100644 --- a/MapControl/WPF/Map.WPF.cs +++ b/MapControl/WPF/Map.WPF.cs @@ -23,16 +23,6 @@ namespace MapControl IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true)); } - public Map() - { - ManipulationStarted += OnManipulationStarted; - ManipulationDelta += OnManipulationDelta; - MouseLeftButtonDown += OnMouseLeftButtonDown; - MouseLeftButtonUp += OnMouseLeftButtonUp; - MouseMove += OnMouseMove; - MouseWheel += OnMouseWheel; - } - /// /// Gets or sets the amount by which the ZoomLevel property changes by a MouseWheel event. /// The default value is 0.25. @@ -52,29 +42,38 @@ namespace MapControl set => SetValue(ManipulationModeProperty, value); } - private void OnManipulationStarted(object sender, ManipulationStartedEventArgs e) + protected override void OnManipulationStarted(ManipulationStartedEventArgs e) { + base.OnManipulationStarted(e); + Manipulation.SetManipulationMode(this, ManipulationMode); } - private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) + protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) { + base.OnManipulationDelta(e); + TransformMap(e.ManipulationOrigin, (Point)e.DeltaManipulation.Translation, e.DeltaManipulation.Rotation, e.DeltaManipulation.Scale.LengthSquared / 2d); } - private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { - if (Keyboard.Modifiers == ModifierKeys.None && CaptureMouse()) + base.OnMouseLeftButtonDown(e); + + if (Keyboard.Modifiers == ModifierKeys.None && + CaptureMouse()) { mousePosition = e.GetPosition(this); } } - private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { + base.OnMouseLeftButtonUp(e); + if (mousePosition.HasValue) { mousePosition = null; @@ -82,18 +81,30 @@ namespace MapControl } } - private void OnMouseMove(object sender, MouseEventArgs e) + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + if (mousePosition.HasValue) { var p = e.GetPosition(this); TranslateMap(new Point(p.X - mousePosition.Value.X, p.Y - mousePosition.Value.Y)); mousePosition = p; } + else if (e.LeftButton == MouseButtonState.Pressed && + Keyboard.Modifiers == ModifierKeys.None && + CaptureMouse()) + { + // Set mousePosition when no MouseLeftButtonDown event was received. + // + mousePosition = e.GetPosition(this); + } } - private void OnMouseWheel(object sender, MouseWheelEventArgs e) + protected override void OnMouseWheel(MouseWheelEventArgs e) { + base.OnMouseWheel(e); + // Standard mouse wheel delta value is 120. // mouseWheelDelta += e.Delta / 120d; diff --git a/MapControl/WPF/MapItem.WPF.cs b/MapControl/WPF/MapItem.WPF.cs index eec68816..b3b97328 100644 --- a/MapControl/WPF/MapItem.WPF.cs +++ b/MapControl/WPF/MapItem.WPF.cs @@ -18,17 +18,57 @@ namespace MapControl DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem))); } + protected override void OnTouchDown(TouchEventArgs e) + { + // Prevent default touch handling. + // + e.Handled = true; + } + + protected override void OnTouchUp(TouchEventArgs e) + { + // Prevent default touch handling. + // + e.Handled = true; + } + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { - if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) + // Prevent default item selection on mouse down. + // + e.Handled = true; + } + + protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) + { + // Prevent default item selection on mouse down. + // + e.Handled = true; + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl) { - e.Handled = true; - MapItemsControl.SelectItemsInRange(this); - } - else - { - base.OnMouseLeftButtonDown(e); + if (mapItemsControl.SelectionMode == SelectionMode.Extended && + Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) + { + mapItemsControl.SelectItemsInRange(this); + } + else + { + // Perform default mouse down item selection on mouse up. + // + base.OnMouseLeftButtonDown(e); + } } + + e.Handled = true; + } + + protected override void OnMouseRightButtonUp(MouseButtonEventArgs e) + { + OnMouseLeftButtonUp(e); } } } diff --git a/MapControl/WinUI/Map.WinUI.cs b/MapControl/WinUI/Map.WinUI.cs index 31921a2a..85989f33 100644 --- a/MapControl/WinUI/Map.WinUI.cs +++ b/MapControl/WinUI/Map.WinUI.cs @@ -20,8 +20,8 @@ namespace MapControl public static readonly DependencyProperty MouseWheelZoomDeltaProperty = DependencyPropertyHelper.Register(nameof(MouseWheelZoomDelta), 0.25); - private uint capturedPointerId; private double mouseWheelDelta; + private bool? manipulationEnabled; public Map() { @@ -32,8 +32,10 @@ namespace MapControl | ManipulationModes.TranslateInertia; ManipulationDelta += OnManipulationDelta; + ManipulationCompleted += OnManipulationCompleted; PointerPressed += OnPointerPressed; - PointerCaptureLost += OnPointerCaptureLost; + PointerReleased += OnPointerReleased; + PointerMoved += OnPointerMoved; PointerWheelChanged += OnPointerWheelChanged; } @@ -49,32 +51,46 @@ namespace MapControl private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) { - if (capturedPointerId != 0) + if (manipulationEnabled.HasValue && manipulationEnabled.Value) { - TranslateMap(e.Delta.Translation); - } - else if (e.PointerDeviceType != PointerDeviceType.Mouse) - { - TransformMap(e.Position, e.Delta.Translation, e.Delta.Rotation, e.Delta.Scale); + if (e.PointerDeviceType == PointerDeviceType.Mouse) + { + TranslateMap(e.Delta.Translation); + } + else + { + TransformMap(e.Position, e.Delta.Translation, e.Delta.Rotation, e.Delta.Scale); + } } } + private void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + manipulationEnabled = null; + } + + private void OnPointerReleased(object sender, PointerRoutedEventArgs e) + { + manipulationEnabled = null; + } + private void OnPointerPressed(object sender, PointerRoutedEventArgs e) { - if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse && - e.KeyModifiers == VirtualKeyModifiers.None && + // IsLeftButtonPressed: input was triggered by the primary action mode of an input device. + // + manipulationEnabled = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && - CapturePointer(e.Pointer)) - { - capturedPointerId = e.Pointer.PointerId; - } + e.KeyModifiers == VirtualKeyModifiers.None; } - private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) + private void OnPointerMoved(object sender, PointerRoutedEventArgs e) { - if (capturedPointerId == e.Pointer.PointerId) + // Set manipulationEnabled when no PointerPressed was received. + // + if (!manipulationEnabled.HasValue && + e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { - capturedPointerId = 0; + manipulationEnabled = e.KeyModifiers == VirtualKeyModifiers.None; } } diff --git a/MapControl/WinUI/MapItem.WinUI.cs b/MapControl/WinUI/MapItem.WinUI.cs index 1a548377..fec42d25 100644 --- a/MapControl/WinUI/MapItem.WinUI.cs +++ b/MapControl/WinUI/MapItem.WinUI.cs @@ -1,10 +1,13 @@ -using Windows.System; +using System; +using Windows.System; #if UWP using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; #else using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; #endif @@ -25,25 +28,44 @@ namespace MapControl item.UpdateMapTransform(newValue); }); + // Used to detect pointer movement between PointerPressed and + // PointerReleased in order to possibly cancel item selection. + // + private const float pointerMovementThreshold = 2f; + private Windows.Foundation.Point pointerPressedPosition; + public MapItem() { DefaultStyleKey = typeof(MapItem); MapPanel.InitMapElement(this); } + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + base.OnPointerPressed(e); + pointerPressedPosition = e.GetCurrentPoint(null).Position; + } + protected override void OnPointerReleased(PointerRoutedEventArgs e) { - // In contrast to WPF and Avalonia, item selection is done on PointerReleased. - // - if (e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift)) + var p = e.GetCurrentPoint(null).Position; + + if (Math.Abs(p.X - pointerPressedPosition.X) <= pointerMovementThreshold && + Math.Abs(p.Y - pointerPressedPosition.Y) <= pointerMovementThreshold && + ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl) { - e.Handled = true; - MapItemsControl.SelectItemsInRange(this); - } - else - { - base.OnPointerReleased(e); + if (mapItemsControl.SelectionMode == SelectionMode.Extended && + e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift)) + { + mapItemsControl.SelectItemsInRange(this); + } + else + { + base.OnPointerReleased(e); + } } + + e.Handled = true; } protected override void OnApplyTemplate()