diff --git a/src/Controls/src/Core/Platform/GestureManager/GestureManager.Gtk.cs b/src/Controls/src/Core/Platform/GestureManager/GestureManager.Gtk.cs index 6275ef1b866d..b1220230187c 100644 --- a/src/Controls/src/Core/Platform/GestureManager/GestureManager.Gtk.cs +++ b/src/Controls/src/Core/Platform/GestureManager/GestureManager.Gtk.cs @@ -1,17 +1,268 @@ #nullable enable using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Gtk; +using Microsoft.Maui.Controls.Internals; namespace Microsoft.Maui.Controls.Platform { + // ported from https://github.com/xamarin/Xamarin.Forms/blob/5.0.0/Xamarin.Forms.Platform.GTK/VisualElementTracker.cs class GestureManager : IDisposable { + readonly IPlatformViewHandler _handler; + readonly NotifyCollectionChangedEventHandler _collectionChangedHandler; + Widget? _container; + Widget? _control; + VisualElement? _element; + + bool _isDisposed; + public GestureManager(IViewHandler handler) { + _handler = (IPlatformViewHandler)handler; + _collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged; + + if (_handler.VirtualView == null) + throw new ArgumentNullException(nameof(handler.VirtualView)); + + if (_handler.PlatformView == null) + throw new ArgumentNullException(nameof(handler.PlatformView)); + + Element = (VisualElement)_handler.VirtualView; + Control = _handler.PlatformView; + + if (_handler.ContainerView != null) + Container = _handler.ContainerView; + else + Container = _handler.PlatformView; + } + + public Widget? Container + { + get { return _container; } + set + { + if (_container == value) + return; + + if (_container != null) + { + _container.ButtonPressEvent -= OnContainerButtonPressEvent; + } + + _container = value; + + UpdatingGestureRecognizers(); + } + } + + public Widget? Control + { + get { return _control; } + set + { + if (_control == value) + return; + + if (_control != null) + { + _control.ButtonPressEvent -= OnControlButtonPressEvent; + } + + _control = value; + + if (PreventGestureBubbling) + { + UpdatingGestureRecognizers(); + } + } + } + + public VisualElement? Element + { + get { return _element; } + set + { + if (_element == value) + return; + + if (_element != null) + { + var view = _element as View; + if (view != null) + { + var oldRecognizers = (ObservableCollection)view.GestureRecognizers; + oldRecognizers.CollectionChanged -= _collectionChangedHandler; + } + } + + _element = value; + + if (_element != null) + { + var view = _element as View; + if (view != null) + { + var newRecognizers = (ObservableCollection)view.GestureRecognizers; + newRecognizers.CollectionChanged += _collectionChangedHandler; + } + } + } + } + + private bool PreventGestureBubbling + { + get + { + return Element switch + { + Button => true, + CheckBox => true, + DatePicker => true, + Stepper => true, + Slider => true, + Switch => true, + TimePicker => true, + ImageButton => true, + RadioButton => true, + _ => false, + }; + } } public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_isDisposed) + return; + + _isDisposed = true; + + if (!disposing) + return; + + if (_container != null) + { + _container.ButtonPressEvent -= OnContainerButtonPressEvent; + } + + if (_element != null) + { + var view = _element as View; + if (view != null) + { + var oldRecognizers = (ObservableCollection)view.GestureRecognizers; + oldRecognizers.CollectionChanged -= _collectionChangedHandler; + } + } + + if (_control != null) + { + _control.ButtonPressEvent -= OnControlButtonPressEvent; + } + + Container?.Destroy(); + Container = null; + } + + private void ModelGestureRecognizersOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + UpdatingGestureRecognizers(); + } + + private void UpdatingGestureRecognizers() + { + var view = Element as View; + var gestures = view?.GestureRecognizers; + + if (_container == null || gestures == null) + return; + + _container.ButtonPressEvent -= OnContainerButtonPressEvent; + + if (gestures.GetGesturesFor().Any() || gestures.GetGesturesFor().Any()) + { + _container.ButtonPressEvent += OnContainerButtonPressEvent; + } + else + { + if (_control != null && PreventGestureBubbling) + { + _control.ButtonPressEvent += OnControlButtonPressEvent; + } + } } + + private void OnContainerButtonPressEvent(object sender, ButtonPressEventArgs args) + { + var button = args.Event.Button; + if (button != 1 && button != 3) + { + return; + } + + var view = Element as View; + + if (view == null) + return; + + int numClicks = 0; + switch (args.Event.Type) + { + case Gdk.EventType.ThreeButtonPress: + numClicks = 3; + break; + case Gdk.EventType.TwoButtonPress: + numClicks = 2; + break; + case Gdk.EventType.ButtonPress: + numClicks = 1; + break; + default: + return; + } + + // Taps or Clicks + if (button == (uint)ButtonsMask.Primary) + { + IEnumerable tapGestures = view.GestureRecognizers + .GetGesturesFor(recognizer => recognizer.NumberOfTapsRequired == numClicks); + + foreach (TapGestureRecognizer recognizer in tapGestures) + recognizer.SendTapped(view); + + IEnumerable clickGestures = view.GestureRecognizers + .GetGesturesFor(recognizer => recognizer.NumberOfClicksRequired == numClicks && + recognizer.Buttons == ButtonsMask.Primary); + + foreach (ClickGestureRecognizer recognizer in clickGestures) + recognizer.SendClicked(view, ButtonsMask.Primary); + } + else + { + // right click + IEnumerable rightClickGestures = view.GestureRecognizers + .GetGesturesFor(recognizer => recognizer.NumberOfClicksRequired == numClicks && + recognizer.Buttons == ButtonsMask.Secondary); + + foreach (ClickGestureRecognizer recognizer in rightClickGestures) + recognizer.SendClicked(view, ButtonsMask.Secondary); + } + } + + private void OnControlButtonPressEvent(object sender, ButtonPressEventArgs args) + { + args.RetVal = true; + } + } } diff --git a/src/Core/src/Platform/Gtk/ContentView.cs b/src/Core/src/Platform/Gtk/ContentView.cs index 1af8361a3309..a5a998f62d4f 100644 --- a/src/Core/src/Platform/Gtk/ContentView.cs +++ b/src/Core/src/Platform/Gtk/ContentView.cs @@ -5,10 +5,10 @@ namespace Microsoft.Maui.Platform { - public class ContentView : Gtk.Box + public class ContentView : Gtk.EventBox { - public ContentView() : base(Orientation.Horizontal, 0) { } + public ContentView() : base() { } internal Func? CrossPlatformMeasure { get; set; } @@ -28,7 +28,7 @@ public Widget? Content } else if (value != null) { - PackStart(value, true, true, 0); + Add(value); } _content = value;