1 ///////////////////////////////////////////////////////////////////////////////// 2 // Paint.NET // 3 // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // 4 // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // 5 // See license-pdn.txt for full licensing and attribution details. // 6 // // 7 // Ported to Pinta by: Jonathan Pobst <monkey@jpobst.com> // 8 ///////////////////////////////////////////////////////////////////////////////// 9 10 using System; 11 using Gdk; 12 using Pinta.Core; 13 14 namespace Pinta.Gui.Widgets 15 { 16 [System.ComponentModel.ToolboxItem (true)] 17 public class AnglePickerGraphic : Gtk.DrawingArea 18 { 19 private bool tracking = false; 20 private Point lastMouseXY; 21 private double angleValue; 22 AnglePickerGraphic()23 public AnglePickerGraphic () 24 { 25 Events = ((Gdk.EventMask)(16134)); 26 27 ButtonPressEvent += HandleHandleButtonPressEvent; 28 ButtonReleaseEvent += HandleHandleButtonReleaseEvent; 29 MotionNotifyEvent += HandleHandleMotionNotifyEvent; 30 } 31 32 #region Public Properties 33 public int Value { 34 get { return (int)angleValue; } 35 set { 36 double v = value % 360; 37 if (angleValue != v) { 38 angleValue = v; 39 OnValueChanged (); 40 this.GdkWindow.Invalidate (); 41 } 42 } 43 } 44 45 public double ValueDouble { 46 get { return angleValue; } 47 set { 48 //double v = Math.IEEERemainder (value, 360.0); 49 if (angleValue != value) { 50 angleValue = value; 51 OnValueChanged (); 52 53 if (GdkWindow != null) 54 GdkWindow.Invalidate (); 55 } 56 } 57 } 58 #endregion 59 60 #region Mouse Handlers HandleHandleMotionNotifyEvent(object o, Gtk.MotionNotifyEventArgs args)61 private void HandleHandleMotionNotifyEvent (object o, Gtk.MotionNotifyEventArgs args) 62 { 63 ProcessMouseEvent (new Point ((int)args.Event.X, (int)args.Event.Y), (args.Event.State & ModifierType.ShiftMask) == ModifierType.ShiftMask); 64 } 65 HandleHandleButtonReleaseEvent(object o, Gtk.ButtonReleaseEventArgs args)66 private void HandleHandleButtonReleaseEvent (object o, Gtk.ButtonReleaseEventArgs args) 67 { 68 tracking = false; 69 } 70 HandleHandleButtonPressEvent(object o, Gtk.ButtonPressEventArgs args)71 private void HandleHandleButtonPressEvent (object o, Gtk.ButtonPressEventArgs args) 72 { 73 tracking = true; 74 75 ProcessMouseEvent (new Point ((int)args.Event.X, (int)args.Event.Y), args.Event.IsShiftPressed ()); 76 } 77 ProcessMouseEvent(Point pt, bool constrainAngle)78 private void ProcessMouseEvent (Point pt, bool constrainAngle) 79 { 80 lastMouseXY = pt; 81 82 if (tracking) { 83 Rectangle ourRect = Rectangle.Inflate (GdkWindow.GetBounds (), -2, -2); 84 int diameter = Math.Min (ourRect.Width, ourRect.Height); 85 Point center = new Point (ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2)); 86 87 int dx = lastMouseXY.X - center.X; 88 int dy = lastMouseXY.Y - center.Y; 89 double theta = Math.Atan2 (-dy, dx); 90 91 double newAngle = (theta * 360) / (2 * Math.PI); 92 93 if (newAngle < 0) 94 newAngle = newAngle + 360; 95 96 if (constrainAngle) { 97 const double constraintAngle = 15.0; 98 99 double multiple = newAngle / constraintAngle; 100 double top = Math.Floor (multiple); 101 double topDelta = Math.Abs (top - multiple); 102 double bottom = Math.Ceiling (multiple); 103 double bottomDelta = Math.Abs (bottom - multiple); 104 105 double bestMultiple; 106 if (bottomDelta < topDelta) { 107 bestMultiple = bottom; 108 } else { 109 bestMultiple = top; 110 } 111 112 newAngle = bestMultiple * constraintAngle; 113 } 114 115 this.ValueDouble = newAngle; 116 117 GdkWindow.Invalidate (); 118 } 119 } 120 #endregion 121 122 #region Drawing Code OnExposeEvent(Gdk.EventExpose ev)123 protected override bool OnExposeEvent (Gdk.EventExpose ev) 124 { 125 base.OnExposeEvent (ev); 126 127 using (Cairo.Context g = CairoHelper.Create (GdkWindow)) { 128 Cairo.Rectangle ourRect = Rectangle.Inflate (GdkWindow.GetBounds (), -1, -1).ToCairoRectangle (); 129 double diameter = Math.Min (ourRect.Width, ourRect.Height); 130 131 double radius = (diameter / 2.0); 132 133 Cairo.PointD center = new Cairo.PointD ( 134 (float)(ourRect.X + radius), 135 (float)(ourRect.Y + radius)); 136 137 double theta = (this.angleValue * 2.0 * Math.PI) / 360.0; 138 139 Cairo.Rectangle ellipseRect = new Cairo.Rectangle (ourRect.Location (), diameter, diameter); 140 Cairo.Rectangle ellipseOutlineRect = ellipseRect; 141 142 g.DrawEllipse (ellipseOutlineRect, new Cairo.Color (.1, .1, .1), 1); 143 144 double endPointRadius = radius - 2; 145 146 Cairo.PointD endPoint = new Cairo.PointD ( 147 (float)(center.X + (endPointRadius * Math.Cos (theta))), 148 (float)(center.Y - (endPointRadius * Math.Sin (theta)))); 149 150 float gripSize = 2.5f; 151 Cairo.Rectangle gripEllipseRect = new Cairo.Rectangle (center.X - gripSize, center.Y - gripSize, gripSize * 2, gripSize * 2); 152 153 g.FillEllipse (gripEllipseRect, new Cairo.Color (.1, .1, .1)); 154 g.DrawLine (center, endPoint, new Cairo.Color (.1, .1, .1), 1); 155 } 156 157 return true; 158 } 159 OnSizeRequested(ref Gtk.Requisition requisition)160 protected override void OnSizeRequested (ref Gtk.Requisition requisition) 161 { 162 // Calculate desired size here. 163 requisition.Height = 50; 164 requisition.Width = 50; 165 } 166 #endregion 167 168 #region Public Events 169 public event EventHandler ValueChanged; 170 OnValueChanged()171 protected virtual void OnValueChanged () 172 { 173 if (ValueChanged != null) { 174 ValueChanged (this, EventArgs.Empty); 175 } 176 } 177 #endregion 178 } 179 } 180