Skip to content

Commit fdf4d7c

Browse files
committed
Add Control.Gestures and Magnification/Pan/Rotate/ScrollGesture
1 parent 7d11b98 commit fdf4d7c

29 files changed

Lines changed: 2181 additions & 8 deletions

lib/monomac

src/Eto.Android/Forms/AndroidControl.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ public Window GetNativeParentWindow()
307307
public virtual void UpdateLayout()
308308
{
309309
}
310+
311+
public virtual void AddGesture(Gesture item) { }
312+
public virtual void ClearGestures() { }
313+
public virtual void RemoveGesture(Gesture item) { }
310314

311315
public ContextMenu ContextMenu { get; set; }
312316

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#if GTKCORE
2+
namespace Eto.GtkSharp.Forms.Controls
3+
{
4+
interface IGtkGestureHandler : Gesture.IHandler
5+
{
6+
void AttachTo(Gtk.Widget widget);
7+
void Detach();
8+
}
9+
10+
public abstract class GestureHandler<TControl, TWidget> : WidgetHandler<TControl, TWidget>, IGtkGestureHandler
11+
where TControl : Gtk.Gesture
12+
where TWidget : Gesture
13+
{
14+
bool _enabled = true;
15+
16+
protected new Gesture.ICallback Callback => (Gesture.ICallback)((ICallbackSource)Widget).Callback;
17+
18+
public bool Enabled
19+
{
20+
get => _enabled;
21+
set
22+
{
23+
_enabled = value;
24+
if (Control != null)
25+
Control.PropagationPhase = value ? Gtk.PropagationPhase.Bubble : Gtk.PropagationPhase.None;
26+
}
27+
}
28+
29+
public void AttachTo(Gtk.Widget widget)
30+
{
31+
Detach();
32+
Control = CreateGesture(widget);
33+
Control.PropagationPhase = _enabled ? Gtk.PropagationPhase.Bubble : Gtk.PropagationPhase.None;
34+
}
35+
36+
public void Detach()
37+
{
38+
if (Control != null)
39+
{
40+
Control.Dispose();
41+
Control = null;
42+
}
43+
}
44+
45+
protected abstract TControl CreateGesture(Gtk.Widget widget);
46+
}
47+
48+
public class MagnificationGestureHandler : GestureHandler<Gtk.GestureZoom, MagnificationGesture>, MagnificationGesture.IHandler
49+
{
50+
double _previousScale = 1.0;
51+
float _magnification;
52+
53+
public float Magnification => _magnification;
54+
55+
protected override Gtk.GestureZoom CreateGesture(Gtk.Widget widget)
56+
{
57+
var gesture = new Gtk.GestureZoom(widget);
58+
gesture.Begin += (sender, e) => _previousScale = gesture.ScaleDelta;
59+
gesture.ScaleChanged += (sender, e) =>
60+
{
61+
var current = gesture.ScaleDelta;
62+
_magnification = _previousScale > 0 ? (float)(current / _previousScale - 1.0) : 0f;
63+
_previousScale = current;
64+
Callback.OnActivated(Widget, EventArgs.Empty);
65+
};
66+
return gesture;
67+
}
68+
}
69+
70+
public class RotationGestureHandler : GestureHandler<Gtk.GestureRotate, RotationGesture>, RotationGesture.IHandler
71+
{
72+
double _previousAngle;
73+
float _rotation;
74+
75+
public float Rotation => _rotation;
76+
77+
protected override Gtk.GestureRotate CreateGesture(Gtk.Widget widget)
78+
{
79+
var gesture = new Gtk.GestureRotate(widget);
80+
gesture.Begin += (sender, e) => _previousAngle = gesture.AngleDelta;
81+
gesture.AngleChanged += (sender, e) =>
82+
{
83+
// AngleDelta is the cumulative rotation in radians since the gesture began.
84+
// Gtk measures counter-clockwise; expose degrees clockwise to match other platforms.
85+
var current = gesture.AngleDelta;
86+
_rotation = (float)(-(current - _previousAngle) * 180.0 / Math.PI);
87+
_previousAngle = current;
88+
if (_rotation == 0f)
89+
return;
90+
Callback.OnActivated(Widget, EventArgs.Empty);
91+
};
92+
return gesture;
93+
}
94+
}
95+
96+
public class PanGestureHandler : GestureHandler<Gtk.GestureDrag, PanGesture>, PanGesture.IHandler
97+
{
98+
PointF _translation;
99+
PointF _velocity;
100+
double _previousX;
101+
double _previousY;
102+
DateTime _previousTime;
103+
104+
public PointF Translation => _translation;
105+
public PointF Velocity => _velocity;
106+
107+
protected override Gtk.GestureDrag CreateGesture(Gtk.Widget widget)
108+
{
109+
var gesture = new Gtk.GestureDrag(widget);
110+
gesture.DragBegin += (sender, e) =>
111+
{
112+
_previousX = 0;
113+
_previousY = 0;
114+
_previousTime = DateTime.UtcNow;
115+
};
116+
gesture.DragUpdate += (sender, e) =>
117+
{
118+
gesture.GetOffset(out var x, out var y);
119+
var now = DateTime.UtcNow;
120+
var dx = x - _previousX;
121+
var dy = y - _previousY;
122+
var dt = (now - _previousTime).TotalSeconds;
123+
_translation = new PointF((float)dx, (float)dy);
124+
_velocity = dt > 0 ? new PointF((float)(dx / dt), (float)(dy / dt)) : PointF.Empty;
125+
_previousX = x;
126+
_previousY = y;
127+
_previousTime = now;
128+
Callback.OnActivated(Widget, EventArgs.Empty);
129+
};
130+
return gesture;
131+
}
132+
}
133+
134+
public class ScrollGestureHandler : WidgetHandler<object, ScrollGesture>, ScrollGesture.IHandler, IGtkGestureHandler
135+
{
136+
Gtk.Widget _widget;
137+
bool _enabled = true;
138+
DateTime _previousTime;
139+
SizeF _delta;
140+
PointF _velocity;
141+
142+
protected new Gesture.ICallback Callback => (Gesture.ICallback)((ICallbackSource)Widget).Callback;
143+
144+
public bool Enabled
145+
{
146+
get => _enabled;
147+
set => _enabled = value;
148+
}
149+
150+
public SizeF Delta => _delta;
151+
public PointF Velocity => _velocity;
152+
153+
public void AttachTo(Gtk.Widget widget)
154+
{
155+
Detach();
156+
_widget = widget;
157+
_widget.AddEvents((int)Gdk.EventMask.ScrollMask);
158+
_widget.ScrollEvent += HandleScrollEvent;
159+
}
160+
161+
public void Detach()
162+
{
163+
if (_widget == null)
164+
return;
165+
_widget.ScrollEvent -= HandleScrollEvent;
166+
_widget = null;
167+
}
168+
169+
void HandleScrollEvent(object o, Gtk.ScrollEventArgs args)
170+
{
171+
if (!Enabled)
172+
return;
173+
174+
var delta = GetDelta(args);
175+
if (delta.IsZero)
176+
return;
177+
178+
var now = DateTime.UtcNow;
179+
var dt = _previousTime == default ? 0 : (now - _previousTime).TotalSeconds;
180+
_previousTime = now;
181+
182+
_delta = delta;
183+
_velocity = dt > 0 ? new PointF((float)(delta.Width / dt), (float)(delta.Height / dt)) : PointF.Empty;
184+
Callback.OnActivated(Widget, EventArgs.Empty);
185+
}
186+
187+
SizeF GetDelta(Gtk.ScrollEventArgs args)
188+
{
189+
var scrollAmount = Widget.WheelScrollAmount;
190+
191+
switch (args.Event.Direction)
192+
{
193+
case Gdk.ScrollDirection.Down:
194+
return new SizeF(0f, -scrollAmount);
195+
case Gdk.ScrollDirection.Left:
196+
return new SizeF(scrollAmount, 0f);
197+
case Gdk.ScrollDirection.Right:
198+
return new SizeF(-scrollAmount, 0f);
199+
case Gdk.ScrollDirection.Up:
200+
return new SizeF(0f, scrollAmount);
201+
case Gdk.ScrollDirection.Smooth:
202+
return new SizeF((float)args.Event.DeltaX * scrollAmount, (float)args.Event.DeltaY * scrollAmount);
203+
default:
204+
return SizeF.Empty;
205+
}
206+
}
207+
}
208+
}
209+
#endif

src/Eto.Gtk/Forms/GtkControl.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,5 +1471,40 @@ public ContextMenu ContextMenu
14711471
}
14721472
}
14731473
}
1474+
1475+
#if GTKCORE
1476+
public virtual void AddGesture(Gesture item)
1477+
{
1478+
if (item == null)
1479+
throw new ArgumentNullException(nameof(item));
1480+
1481+
var handler = ((IHandlerSource)item).Handler as Eto.GtkSharp.Forms.Controls.IGtkGestureHandler;
1482+
if (handler == null)
1483+
throw new NotSupportedException($"Gesture '{item.GetType().FullName}' is not supported on GTK");
1484+
1485+
handler.AttachTo(EventControl);
1486+
}
1487+
1488+
public virtual void ClearGestures()
1489+
{
1490+
foreach (var gesture in Widget.Gestures)
1491+
{
1492+
if (((IHandlerSource)gesture).Handler is Eto.GtkSharp.Forms.Controls.IGtkGestureHandler handler)
1493+
handler.Detach();
1494+
}
1495+
}
1496+
1497+
public virtual void RemoveGesture(Gesture item)
1498+
{
1499+
if (item == null)
1500+
return;
1501+
if (((IHandlerSource)item).Handler is Eto.GtkSharp.Forms.Controls.IGtkGestureHandler handler)
1502+
handler.Detach();
1503+
}
1504+
#else
1505+
public virtual void AddGesture(Gesture item) { }
1506+
public virtual void ClearGestures() { }
1507+
public virtual void RemoveGesture(Gesture item) { }
1508+
#endif
14741509
}
14751510
}

src/Eto.Gtk/Platform.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ public static void AddTo(Eto.Platform p)
111111
p.Add<RadialGradientBrush.IHandler>(() => new RadialGradientBrushHandler());
112112
p.Add<SystemColors.IHandler>(() => new SystemColorsHandler());
113113
p.Add<FormattedText.IHandler>(() => new FormattedTextHandler());
114+
#if GTKCORE
115+
p.Add<MagnificationGesture.IHandler>(() => new MagnificationGestureHandler());
116+
p.Add<RotationGesture.IHandler>(() => new RotationGestureHandler());
117+
p.Add<PanGesture.IHandler>(() => new PanGestureHandler());
118+
p.Add<ScrollGesture.IHandler>(() => new ScrollGestureHandler());
119+
#endif
114120

115121
// Forms.Cells
116122
p.Add<CheckBoxCell.IHandler>(() => new CheckBoxCellHandler());

0 commit comments

Comments
 (0)