ArrowButton.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. using System;
  2. using System.ComponentModel;
  3. using System.Drawing;
  4. using System.Drawing.Drawing2D;
  5. using System.Drawing.Imaging;
  6. using System.Windows.Forms;
  7. using System.Windows.Forms.VisualStyles;
  8. namespace PaintDotNet
  9. {
  10. public sealed class ArrowButton
  11. : ButtonBase
  12. {
  13. public bool drawTransparentBackColor = true;
  14. private ArrowDirection arrowDirection = ArrowDirection.Right;
  15. private bool drawWithGradient = false;
  16. private bool reverseArrowColors = false;
  17. private float arrowOutlineWidth = 1.0f;
  18. private bool forcedPushed = false;
  19. private Surface backBufferSurface = null;
  20. private RenderArgs backBuffer = null;
  21. public bool ForcedPushedAppearance
  22. {
  23. get
  24. {
  25. return this.forcedPushed;
  26. }
  27. set
  28. {
  29. if (this.forcedPushed != value)
  30. {
  31. this.forcedPushed = value;
  32. Invalidate();
  33. }
  34. }
  35. }
  36. public bool ReverseArrowColors
  37. {
  38. get
  39. {
  40. return this.reverseArrowColors;
  41. }
  42. set
  43. {
  44. if (this.reverseArrowColors != value)
  45. {
  46. this.reverseArrowColors = value;
  47. Invalidate();
  48. }
  49. }
  50. }
  51. public float ArrowOutlineWidth
  52. {
  53. get
  54. {
  55. return this.arrowOutlineWidth;
  56. }
  57. set
  58. {
  59. if (this.arrowOutlineWidth != value)
  60. {
  61. this.arrowOutlineWidth = value;
  62. Invalidate();
  63. }
  64. }
  65. }
  66. public ArrowDirection ArrowDirection
  67. {
  68. get
  69. {
  70. return this.arrowDirection;
  71. }
  72. set
  73. {
  74. if (this.arrowDirection != value)
  75. {
  76. this.arrowDirection = value;
  77. Invalidate();
  78. }
  79. }
  80. }
  81. public bool DrawWithGradient
  82. {
  83. get
  84. {
  85. return this.drawWithGradient;
  86. }
  87. set
  88. {
  89. if (this.drawWithGradient != value)
  90. {
  91. this.drawWithGradient = value;
  92. Invalidate();
  93. }
  94. }
  95. }
  96. public ArrowButton()
  97. {
  98. InitializeComponent();
  99. }
  100. private void InitializeComponent()
  101. {
  102. this.BackColor = this.drawTransparentBackColor ? Color.Transparent : Color.FromArgb(192, SystemColors.ControlDark);
  103. }
  104. protected override void Dispose(bool disposing)
  105. {
  106. if (disposing)
  107. {
  108. if (this.backBuffer != null)
  109. {
  110. this.backBuffer.Dispose();
  111. this.backBuffer = null;
  112. }
  113. if (this.backBufferSurface != null)
  114. {
  115. this.backBufferSurface.Dispose();
  116. this.backBufferSurface = null;
  117. }
  118. }
  119. base.Dispose(disposing);
  120. }
  121. protected override void OnPaintButton(Graphics g, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues)
  122. {
  123. PushButtonState newState;
  124. if (this.forcedPushed)
  125. {
  126. newState = PushButtonState.Pressed;
  127. }
  128. else
  129. {
  130. newState = state;
  131. }
  132. OnPaintButtonImpl(g, newState, drawFocusCues, drawKeyboardCues);
  133. }
  134. private void OnPaintButtonImpl(Graphics g, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues)
  135. {
  136. Color backColor;
  137. Color outlineColor;
  138. Color arrowFillColor;
  139. Color arrowOutlineColor;
  140. switch (state)
  141. {
  142. case PushButtonState.Disabled:
  143. backColor = this.drawTransparentBackColor ? Color.Transparent : Color.FromArgb(192, SystemColors.ControlDark);
  144. outlineColor = BackColor;
  145. arrowFillColor = Color.Gray;
  146. arrowOutlineColor = Color.Black;
  147. break;
  148. case PushButtonState.Hot:
  149. backColor = Color.FromArgb(64, SystemColors.HotTrack);
  150. outlineColor = backColor;
  151. arrowFillColor = Color.Blue;
  152. arrowOutlineColor = Color.White;
  153. break;
  154. case PushButtonState.Default:
  155. case PushButtonState.Normal:
  156. backColor = this.drawTransparentBackColor ? Color.Transparent : Color.FromArgb(192, SystemColors.ControlDark);
  157. outlineColor = Color.Transparent;
  158. arrowFillColor = Color.Black;
  159. arrowOutlineColor = Color.White;
  160. break;
  161. case PushButtonState.Pressed:
  162. backColor = Color.FromArgb(192, SystemColors.Highlight);
  163. outlineColor = Color.FromArgb(192, SystemColors.Highlight);
  164. arrowFillColor = Color.Blue;
  165. arrowOutlineColor = Color.White;
  166. break;
  167. default:
  168. throw new InvalidEnumArgumentException("buttonState");
  169. }
  170. // Draw parent background
  171. IPaintBackground asIpb = Parent as IPaintBackground;
  172. if (!this.drawWithGradient || asIpb == null)
  173. {
  174. if (asIpb != null)
  175. {
  176. Rectangle screenRect = RectangleToScreen(ClientRectangle);
  177. Rectangle parentRect = Parent.RectangleToClient(screenRect);
  178. g.TranslateTransform(-Left, -Top, MatrixOrder.Append);
  179. asIpb.PaintBackground(g, parentRect);
  180. g.TranslateTransform(+Left, +Top, MatrixOrder.Append);
  181. }
  182. else
  183. {
  184. using (SolidBrush backBrush = new SolidBrush(BackColor))
  185. {
  186. g.FillRectangle(backBrush, ClientRectangle);
  187. }
  188. }
  189. }
  190. else
  191. {
  192. if (this.backBufferSurface != null &&
  193. (this.backBufferSurface.Width != ClientSize.Width || this.backBufferSurface.Height != ClientSize.Height))
  194. {
  195. this.backBuffer.Dispose();
  196. this.backBuffer = null;
  197. this.backBufferSurface.Dispose();
  198. this.backBufferSurface = null;
  199. }
  200. if (this.backBufferSurface == null)
  201. {
  202. this.backBufferSurface = new Surface(ClientSize.Width, ClientSize.Height);
  203. this.backBuffer = new RenderArgs(this.backBufferSurface);
  204. }
  205. Rectangle screenRect = RectangleToScreen(ClientRectangle);
  206. Rectangle parentRect = Parent.RectangleToClient(screenRect);
  207. using (Graphics bg = Graphics.FromImage(this.backBuffer.Bitmap))
  208. {
  209. bg.TranslateTransform(-Left, -Top, MatrixOrder.Append);
  210. asIpb.PaintBackground(bg, parentRect);
  211. }
  212. BitmapData bitmapData = this.backBuffer.Bitmap.LockBits(
  213. new Rectangle(0, 0, this.backBuffer.Bitmap.Width, this.backBuffer.Bitmap.Height),
  214. ImageLockMode.ReadWrite,
  215. PixelFormat.Format32bppArgb);
  216. int startAlpha;
  217. int finishAlpha;
  218. if (this.arrowDirection == ArrowDirection.Left || this.arrowDirection == ArrowDirection.Up)
  219. {
  220. startAlpha = 255;
  221. finishAlpha = 0;
  222. }
  223. else if (this.arrowDirection == ArrowDirection.Right || this.ArrowDirection == ArrowDirection.Down)
  224. {
  225. startAlpha = 0;
  226. finishAlpha = 255;
  227. }
  228. else
  229. {
  230. throw new InvalidEnumArgumentException("this.arrowDirection");
  231. }
  232. unsafe
  233. {
  234. if (this.arrowDirection == ArrowDirection.Left || this.arrowDirection == ArrowDirection.Right)
  235. {
  236. for (int x = 0; x < this.backBuffer.Bitmap.Width; ++x)
  237. {
  238. float lerp = (float)x / (float)(this.backBuffer.Bitmap.Width - 1);
  239. if (this.arrowDirection == ArrowDirection.Left)
  240. {
  241. lerp = 1.0f - (float)Math.Cos(lerp * (Math.PI / 2.0));
  242. }
  243. else
  244. {
  245. lerp = (float)Math.Sin(lerp * (Math.PI / 2.0));
  246. }
  247. byte alpha = (byte)(startAlpha + ((int)(lerp * (finishAlpha - startAlpha))));
  248. byte* pb = (byte*)bitmapData.Scan0.ToPointer() + (x * 4) + 3; // *4 because 4-bytes per pixel, +3 to get to alpha channel
  249. for (int y = 0; y < this.backBuffer.Bitmap.Height; ++y)
  250. {
  251. *pb = alpha;
  252. pb += bitmapData.Stride;
  253. }
  254. }
  255. }
  256. else if (this.arrowDirection == ArrowDirection.Up || this.arrowDirection == ArrowDirection.Down)
  257. {
  258. for (int y = 0; y < this.backBuffer.Bitmap.Height; ++y)
  259. {
  260. float lerp = (float)y / (float)(this.backBuffer.Bitmap.Height - 1);
  261. lerp = 1.0f - (float)Math.Cos(lerp * (Math.PI / 2.0));
  262. byte alpha = (byte)(startAlpha + ((int)(lerp * (finishAlpha - startAlpha))));
  263. byte* pb = (byte*)bitmapData.Scan0.ToPointer() + (y * bitmapData.Stride) + 3; // *Stride for access to start of row, +3 to get to alpha channel
  264. for (int x = 0; x < this.backBuffer.Bitmap.Width; ++x)
  265. {
  266. *pb = alpha;
  267. pb += 4; // 4 for byte size of pixel
  268. }
  269. }
  270. }
  271. }
  272. this.backBuffer.Bitmap.UnlockBits(bitmapData);
  273. bitmapData = null;
  274. g.DrawImage(this.backBuffer.Bitmap, new Point(0, 0));
  275. }
  276. using (SolidBrush fillBrush = new SolidBrush(backColor))
  277. {
  278. g.FillRectangle(fillBrush, ClientRectangle);
  279. }
  280. // Draw outline
  281. using (Pen outlinePen = new Pen(outlineColor))
  282. {
  283. g.DrawRectangle(outlinePen, new Rectangle(0, 0, ClientSize.Width - 1, ClientSize.Height - 1));
  284. }
  285. // Draw button
  286. g.SmoothingMode = SmoothingMode.AntiAlias;
  287. const int arrowInset = 3;
  288. int arrowSize = Math.Min(ClientSize.Width - arrowInset * 2, ClientSize.Height - arrowInset * 2) - 1;
  289. PointF a;
  290. PointF b;
  291. PointF c;
  292. switch (this.arrowDirection)
  293. {
  294. case ArrowDirection.Left:
  295. a = new PointF(arrowInset, ClientSize.Height / 2);
  296. b = new PointF(ClientSize.Width - arrowInset, (ClientSize.Height - arrowSize) / 2);
  297. c = new PointF(ClientSize.Width - arrowInset, (ClientSize.Height + arrowSize) / 2);
  298. break;
  299. case ArrowDirection.Right:
  300. a = new PointF(ClientSize.Width - arrowInset, ClientSize.Height / 2);
  301. b = new PointF(arrowInset, (ClientSize.Height - arrowSize) / 2);
  302. c = new PointF(arrowInset, (ClientSize.Height + arrowSize) / 2);
  303. break;
  304. case ArrowDirection.Up:
  305. a = new PointF(ClientSize.Width / 2, (ClientSize.Height - arrowSize) / 2);
  306. b = new PointF((ClientSize.Width - arrowSize) / 2, (ClientSize.Height + arrowSize) / 2);
  307. c = new PointF((ClientSize.Width + arrowSize) / 2, (ClientSize.Height + arrowSize) / 2);
  308. break;
  309. case ArrowDirection.Down:
  310. a = new PointF(ClientSize.Width / 2, (ClientSize.Height + arrowSize) / 2);
  311. b = new PointF((ClientSize.Width - arrowSize) / 2, (ClientSize.Height - arrowSize) / 2);
  312. c = new PointF((ClientSize.Width + arrowSize) / 2, (ClientSize.Height - arrowSize) / 2);
  313. break;
  314. default:
  315. throw new InvalidEnumArgumentException("this.arrowDirection");
  316. }
  317. // SPIKE in order to get this rendering correctly right away
  318. if (this.arrowDirection == ArrowDirection.Down)
  319. {
  320. SmoothingMode oldSM = g.SmoothingMode;
  321. g.SmoothingMode = SmoothingMode.None;
  322. float top = b.Y - 2;
  323. float left = b.X;
  324. float right = c.X;
  325. int squareCount = (int)((right - left) / 3);
  326. Brush outlineBrush = new SolidBrush(arrowOutlineColor);
  327. Brush interiorBrush = new SolidBrush(arrowFillColor);
  328. g.FillRectangle(interiorBrush, left, top, right - left + 1, 3);
  329. ++left;
  330. while (left < right)
  331. {
  332. RectangleF rect = new RectangleF(left, top + 1, 1, 1);
  333. g.FillRectangle(outlineBrush, rect);
  334. left += 2;
  335. }
  336. outlineBrush.Dispose();
  337. outlineBrush = null;
  338. interiorBrush.Dispose();
  339. interiorBrush = null;
  340. a.Y += 2;
  341. b.Y += 2;
  342. c.Y += 2;
  343. g.SmoothingMode = oldSM;
  344. }
  345. if (this.reverseArrowColors)
  346. {
  347. Utility.Swap(ref arrowFillColor, ref arrowOutlineColor);
  348. }
  349. using (Brush buttonBrush = new SolidBrush(arrowFillColor))
  350. {
  351. g.FillPolygon(buttonBrush, new PointF[] { a, b, c });
  352. }
  353. using (Pen buttonPen = new Pen(arrowOutlineColor, this.arrowOutlineWidth))
  354. {
  355. g.DrawPolygon(buttonPen, new PointF[] { a, b, c });
  356. }
  357. }
  358. }
  359. }