PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Core/ImageStrip.cs

https://bitbucket.org/tuldok89/openpdn
C# | 1387 lines | 1097 code | 235 blank | 55 comment | 121 complexity | 3ff36a48e0243580cfbbd3ef60bb71f2 MD5 | raw file
  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 src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.Base;
  10. using PaintDotNet.SystemLayer;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.ComponentModel;
  14. using System.Drawing;
  15. using System.Drawing.Drawing2D;
  16. using System.Windows.Forms;
  17. using System.Windows.Forms.VisualStyles;
  18. namespace PaintDotNet
  19. {
  20. /*
  21. * Coordinate spaces:
  22. *
  23. * - Client -- This is the same as the Windows client space.
  24. * - Screen -- This is the same as the Windows screen space.
  25. * - View -- This is a virtual coordinate in the viewable, scrollable area. Client->View is defined as Client.X+ScrollOffset. View->Client is View.X-ScrollOffset
  26. * - Item -- This is relative to an item. Item->View is Item.X+(itemIndex * ItemViewSize.Width)
  27. *
  28. * */
  29. public class ImageStrip
  30. : Control
  31. {
  32. public enum ItemPart
  33. {
  34. None,
  35. Image,
  36. CloseButton
  37. }
  38. public sealed class Item
  39. {
  40. private PushButtonState _imageRenderState;
  41. private Image _image;
  42. private bool _selected;
  43. private PushButtonState _checkRenderState;
  44. private CheckState _checkState;
  45. private PushButtonState _closeRenderState;
  46. private bool _dirty;
  47. private bool _lockedDirtyValue;
  48. private int _dirtyValueLockCount;
  49. private object _tag;
  50. public event EventHandler Changed;
  51. private void OnChanged()
  52. {
  53. if (Changed != null)
  54. {
  55. Changed(this, EventArgs.Empty);
  56. }
  57. }
  58. public Image Image
  59. {
  60. get
  61. {
  62. return _image;
  63. }
  64. set
  65. {
  66. _image = value;
  67. OnChanged();
  68. }
  69. }
  70. public PushButtonState ImageRenderState
  71. {
  72. get
  73. {
  74. return _imageRenderState;
  75. }
  76. set
  77. {
  78. if (_imageRenderState == value) return;
  79. _imageRenderState = value;
  80. OnChanged();
  81. }
  82. }
  83. public bool Selected
  84. {
  85. get
  86. {
  87. return _selected;
  88. }
  89. set
  90. {
  91. if (_selected == value) return;
  92. _selected = value;
  93. OnChanged();
  94. }
  95. }
  96. public bool Dirty
  97. {
  98. get {
  99. return _dirtyValueLockCount > 0 ? _lockedDirtyValue : _dirty;
  100. }
  101. set
  102. {
  103. if (_dirty == value) return;
  104. _dirty = value;
  105. if (_dirtyValueLockCount <= 0)
  106. {
  107. OnChanged();
  108. }
  109. }
  110. }
  111. public void LockDirtyValue(bool forceValue)
  112. {
  113. ++_dirtyValueLockCount;
  114. if (_dirtyValueLockCount == 1)
  115. {
  116. _lockedDirtyValue = forceValue;
  117. }
  118. }
  119. public void UnlockDirtyValue()
  120. {
  121. --_dirtyValueLockCount;
  122. if (_dirtyValueLockCount == 0)
  123. {
  124. OnChanged();
  125. }
  126. else if (_dirtyValueLockCount < 0)
  127. {
  128. throw new InvalidOperationException("Calls to UnlockDirtyValue() must be matched by a preceding call to LockDirtyValue()");
  129. }
  130. }
  131. public bool Checked
  132. {
  133. get
  134. {
  135. return (CheckState == CheckState.Checked);
  136. }
  137. set {
  138. CheckState = value ? CheckState.Checked : CheckState.Unchecked;
  139. }
  140. }
  141. public CheckState CheckState
  142. {
  143. get
  144. {
  145. return _checkState;
  146. }
  147. set
  148. {
  149. if (_checkState == value) return;
  150. _checkState = value;
  151. OnChanged();
  152. }
  153. }
  154. public PushButtonState CheckRenderState
  155. {
  156. get
  157. {
  158. return _checkRenderState;
  159. }
  160. set
  161. {
  162. if (_checkRenderState == value) return;
  163. _checkRenderState = value;
  164. OnChanged();
  165. }
  166. }
  167. public PushButtonState CloseRenderState
  168. {
  169. get
  170. {
  171. return _closeRenderState;
  172. }
  173. set
  174. {
  175. if (_closeRenderState == value) return;
  176. _closeRenderState = value;
  177. OnChanged();
  178. }
  179. }
  180. public void SetPartRenderState(ItemPart itemPart, PushButtonState renderState)
  181. {
  182. switch (itemPart)
  183. {
  184. case ItemPart.None:
  185. break;
  186. case ItemPart.CloseButton:
  187. CloseRenderState = renderState;
  188. break;
  189. case ItemPart.Image:
  190. ImageRenderState = renderState;
  191. break;
  192. default:
  193. throw new InvalidEnumArgumentException();
  194. }
  195. }
  196. public object Tag
  197. {
  198. get
  199. {
  200. return _tag;
  201. }
  202. set
  203. {
  204. _tag = value;
  205. OnChanged();
  206. }
  207. }
  208. public void Update()
  209. {
  210. OnChanged();
  211. }
  212. public Item()
  213. {
  214. }
  215. public Item(Image image)
  216. {
  217. _image = image;
  218. }
  219. }
  220. private bool _managedFocus;
  221. private bool _showScrollButtons;
  222. private int _scrollOffset;
  223. private bool _showCloseButtons;
  224. private const int CloseButtonLength = 13;
  225. private const int ImagePadding = 2;
  226. private const int CloseButtonPadding = 2;
  227. private int _mouseOverIndex = -1;
  228. private ItemPart _mouseOverItemPart = ItemPart.None;
  229. private bool _mouseOverApplyRendering;
  230. private int _mouseDownIndex = -1;
  231. private MouseButtons _mouseDownButton = MouseButtons.None;
  232. private ItemPart _mouseDownItemPart = ItemPart.None;
  233. private bool _mouseDownApplyRendering;
  234. private bool _drawShadow = true;
  235. private bool _drawDirtyOverlay = true;
  236. public bool DrawShadow
  237. {
  238. get
  239. {
  240. return _drawShadow;
  241. }
  242. set
  243. {
  244. if (_drawShadow == value) return;
  245. _drawShadow = value;
  246. Refresh();
  247. }
  248. }
  249. public bool DrawDirtyOverlay
  250. {
  251. get
  252. {
  253. return _drawDirtyOverlay;
  254. }
  255. set
  256. {
  257. if (_drawDirtyOverlay == value) return;
  258. _drawDirtyOverlay = value;
  259. Refresh();
  260. }
  261. }
  262. // This is done as an optimization: otherwise we're getting flooded with MouseMove events
  263. // and constantly refreshing our rendering. So CPU usage goes to heck.
  264. private Point _lastMouseMovePt = new Point(-32000, -32000);
  265. private readonly List<Item> _items = new List<Item>();
  266. protected ArrowButton LeftScrollButton { get; private set; }
  267. protected ArrowButton RightScrollButton { get; private set; }
  268. private void MouseStatesToItemStates()
  269. {
  270. UI.SuspendControlPainting(this);
  271. foreach (Item t in _items)
  272. {
  273. t.CheckRenderState = PushButtonState.Normal;
  274. t.CloseRenderState = PushButtonState.Normal;
  275. t.ImageRenderState = PushButtonState.Normal;
  276. t.Selected = false;
  277. }
  278. if (_mouseDownApplyRendering)
  279. {
  280. if (_mouseDownIndex < 0 || _mouseDownIndex >= _items.Count)
  281. {
  282. _mouseDownApplyRendering = false;
  283. }
  284. else
  285. {
  286. _items[_mouseDownIndex].SetPartRenderState(_mouseDownItemPart, PushButtonState.Pressed);
  287. _items[_mouseDownIndex].Selected = true;
  288. }
  289. }
  290. else if (_mouseOverApplyRendering)
  291. {
  292. if (_mouseOverIndex < 0 || _mouseOverIndex >= _items.Count)
  293. {
  294. _mouseOverApplyRendering = false;
  295. }
  296. else
  297. {
  298. _items[_mouseOverIndex].SetPartRenderState(_mouseOverItemPart, PushButtonState.Hot);
  299. _items[_mouseOverIndex].Selected = true;
  300. }
  301. }
  302. UI.ResumeControlPainting(this);
  303. Invalidate();
  304. }
  305. public ImageStrip()
  306. {
  307. SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
  308. SetStyle(ControlStyles.Selectable, false);
  309. DoubleBuffered = true;
  310. ResizeRedraw = true;
  311. InitializeComponent();
  312. }
  313. private void InitializeComponent()
  314. {
  315. LeftScrollButton = new ArrowButton();
  316. RightScrollButton = new ArrowButton();
  317. SuspendLayout();
  318. //
  319. // leftScrollButton
  320. //
  321. LeftScrollButton.Name = "LeftScrollButton";
  322. LeftScrollButton.ArrowDirection = ArrowDirection.Left;
  323. LeftScrollButton.ArrowOutlineWidth = 1.0f;
  324. LeftScrollButton.Click += LeftScrollButtonClick;
  325. LeftScrollButton.DrawWithGradient = true;
  326. //
  327. // rightScrollButton
  328. //
  329. RightScrollButton.Name = "RightScrollButton";
  330. RightScrollButton.ArrowDirection = ArrowDirection.Right;
  331. RightScrollButton.ArrowOutlineWidth = 1.0f;
  332. RightScrollButton.Click += RightScrollButtonClick;
  333. RightScrollButton.DrawWithGradient = true;
  334. //
  335. // ImageStrip
  336. //
  337. Name = "ImageStrip";
  338. TabStop = false;
  339. Controls.Add(LeftScrollButton);
  340. Controls.Add(RightScrollButton);
  341. ResumeLayout();
  342. PerformLayout();
  343. }
  344. public event EventHandler<EventArgs<ArrowDirection>> ScrollArrowClicked;
  345. protected void OnScrollArrowClicked(ArrowDirection arrowDirection)
  346. {
  347. if (ScrollArrowClicked != null)
  348. {
  349. ScrollArrowClicked(this, new EventArgs<ArrowDirection>(arrowDirection));
  350. }
  351. }
  352. private void LeftScrollButtonClick(object sender, EventArgs e)
  353. {
  354. Focus();
  355. OnScrollArrowClicked(ArrowDirection.Left);
  356. }
  357. private void RightScrollButtonClick(object sender, EventArgs e)
  358. {
  359. Focus();
  360. OnScrollArrowClicked(ArrowDirection.Right);
  361. }
  362. /// <summary>
  363. /// This event is raised when this control wishes to relinquish focus.
  364. /// </summary>
  365. public event EventHandler RelinquishFocus;
  366. private void OnRelinquishFocus()
  367. {
  368. if (RelinquishFocus != null)
  369. {
  370. RelinquishFocus(this, EventArgs.Empty);
  371. }
  372. }
  373. /// <summary>
  374. /// Gets or sets whether the control manages focus.
  375. /// </summary>
  376. /// <remarks>
  377. /// If this is true, the toolstrip will capture focus when the mouse enters its client area. It will then
  378. /// relinquish focus (via the RelinquishFocus event) when the mouse leaves. It will not capture or
  379. /// attempt to relinquish focus if MenuStripEx.IsAnyMenuActive returns true.
  380. /// </remarks>
  381. public bool ManagedFocus
  382. {
  383. get
  384. {
  385. return _managedFocus;
  386. }
  387. set
  388. {
  389. _managedFocus = value;
  390. }
  391. }
  392. public void AddItem(Item newItem)
  393. {
  394. if (_items.Contains(newItem))
  395. {
  396. throw new ArgumentException("newItem was already added to this control");
  397. }
  398. newItem.Changed += ItemChanged;
  399. _items.Add(newItem);
  400. PerformLayout();
  401. Invalidate();
  402. }
  403. public void RemoveItem(Item item)
  404. {
  405. if (!_items.Contains(item))
  406. {
  407. throw new ArgumentException("item was never added to this control");
  408. }
  409. item.Changed -= ItemChanged;
  410. _items.Remove(item);
  411. PerformLayout();
  412. Invalidate();
  413. }
  414. public void ClearItems()
  415. {
  416. SuspendLayout();
  417. UI.SuspendControlPainting(this);
  418. while (_items.Count > 0)
  419. {
  420. RemoveItem(_items[_items.Count - 1]);
  421. }
  422. UI.ResumeControlPainting(this);
  423. ResumeLayout(true);
  424. Invalidate();
  425. }
  426. private void ItemChanged(object sender, EventArgs e)
  427. {
  428. Invalidate();
  429. }
  430. /// <summary>
  431. /// Raised when an item is clicked on.
  432. /// </summary>
  433. /// <remarks>
  434. /// e.Data.First is a reference to the Item.
  435. /// e.Data.Second is the ItemPart.
  436. /// e.Data.Third is the MouseButtons that was used to click on the ItemPart.
  437. /// </remarks>
  438. public event EventHandler<EventArgs<Triple<Item, ItemPart, MouseButtons>>> ItemClicked;
  439. protected virtual void OnItemClicked(Item item, ItemPart itemPart, MouseButtons mouseButtons)
  440. {
  441. if (ItemClicked != null)
  442. {
  443. ItemClicked(this, new EventArgs<Triple<Item, ItemPart, MouseButtons>>(
  444. Triple.Create(item, itemPart, mouseButtons)));
  445. }
  446. }
  447. public void PerformItemClick(int itemIndex, ItemPart itemPart, MouseButtons mouseButtons)
  448. {
  449. PerformItemClick(_items[itemIndex], itemPart, mouseButtons);
  450. }
  451. public void PerformItemClick(Item item, ItemPart itemPart, MouseButtons mouseButtons)
  452. {
  453. OnItemClicked(item, itemPart, mouseButtons);
  454. }
  455. public Item[] Items
  456. {
  457. get
  458. {
  459. return _items.ToArray();
  460. }
  461. }
  462. public int ItemCount
  463. {
  464. get
  465. {
  466. return _items.Count;
  467. }
  468. }
  469. public bool ShowScrollButtons
  470. {
  471. get
  472. {
  473. return _showScrollButtons;
  474. }
  475. set
  476. {
  477. if (_showScrollButtons == value) return;
  478. _showScrollButtons = value;
  479. PerformLayout();
  480. Invalidate(true);
  481. }
  482. }
  483. public int MinScrollOffset
  484. {
  485. get
  486. {
  487. return 0;
  488. }
  489. }
  490. public int MaxScrollOffset
  491. {
  492. get
  493. {
  494. Size itemSize = ItemSize;
  495. int itemsLength = itemSize.Width * _items.Count;
  496. int viewLength = itemsLength - ClientSize.Width;
  497. int maxScrollOffset = Math.Max(0, viewLength);
  498. return maxScrollOffset;
  499. }
  500. }
  501. public int ScrollOffset
  502. {
  503. get
  504. {
  505. return _scrollOffset;
  506. }
  507. set
  508. {
  509. int clampedValue = Utility.Clamp(value, MinScrollOffset, MaxScrollOffset);
  510. if (_scrollOffset == clampedValue) return;
  511. _scrollOffset = clampedValue;
  512. OnScrollOffsetChanged();
  513. Invalidate(true);
  514. }
  515. }
  516. public event EventHandler ScrollOffsetChanged;
  517. private void OnScrollOffsetChanged()
  518. {
  519. PerformLayout();
  520. if (ScrollOffsetChanged != null)
  521. {
  522. ScrollOffsetChanged(this, EventArgs.Empty);
  523. }
  524. }
  525. /// <summary>
  526. /// Gets the viewable area, in View coordinate space.
  527. /// </summary>
  528. public Rectangle ViewRectangle
  529. {
  530. get
  531. {
  532. Size itemSize = ItemSize;
  533. return new Rectangle(0, 0, itemSize.Width * ItemCount, itemSize.Height);
  534. }
  535. }
  536. protected override void OnLayout(LayoutEventArgs levent)
  537. {
  538. int arrowWidth = UI.ScaleWidth(16);
  539. ScrollOffset = Utility.Clamp(_scrollOffset, MinScrollOffset, MaxScrollOffset);
  540. // Determine arrow visibility / position
  541. LeftScrollButton.Size = new Size(arrowWidth, ClientSize.Height);
  542. LeftScrollButton.Location = new Point(0, 0);
  543. RightScrollButton.Size = new Size(arrowWidth, ClientSize.Height);
  544. RightScrollButton.Location = new Point(ClientSize.Width - RightScrollButton.Width, 0);
  545. bool showEitherButton = _showScrollButtons && (ViewRectangle.Width > ClientRectangle.Width);
  546. bool showRightButton = (_scrollOffset < MaxScrollOffset) && showEitherButton;
  547. bool showLeftButton = (_scrollOffset > MinScrollOffset) && showEitherButton;
  548. RightScrollButton.Enabled = showRightButton;
  549. RightScrollButton.Visible = showRightButton;
  550. LeftScrollButton.Enabled = showLeftButton;
  551. LeftScrollButton.Visible = showLeftButton;
  552. base.OnLayout(levent);
  553. }
  554. public bool ShowCloseButtons
  555. {
  556. get
  557. {
  558. return _showCloseButtons;
  559. }
  560. set
  561. {
  562. if (_showCloseButtons == value) return;
  563. _showCloseButtons = value;
  564. PerformLayout();
  565. Invalidate();
  566. }
  567. }
  568. public int PreferredMinClientWidth
  569. {
  570. get
  571. {
  572. if (_items.Count == 0)
  573. {
  574. return 0;
  575. }
  576. int minWidth = ItemSize.Width;
  577. if (LeftScrollButton.Visible || RightScrollButton.Visible)
  578. {
  579. minWidth += LeftScrollButton.Width;
  580. minWidth += RightScrollButton.Width;
  581. }
  582. minWidth = Math.Min(minWidth, ViewRectangle.Width);
  583. return minWidth;
  584. }
  585. }
  586. public Size PreferredImageSize
  587. {
  588. get
  589. {
  590. Rectangle itemRect;
  591. Rectangle imageRect;
  592. MeasureItemPartRectangles(out itemRect, out imageRect);
  593. return new Size(imageRect.Width - ImagePadding * 2, imageRect.Height - ImagePadding * 2);
  594. }
  595. }
  596. public Size ItemSize
  597. {
  598. get
  599. {
  600. Rectangle itemRect;
  601. Rectangle imageRect;
  602. MeasureItemPartRectangles(out itemRect, out imageRect);
  603. return itemRect.Size;
  604. }
  605. }
  606. protected virtual void DrawItemBackground(Graphics g, Item item, Rectangle itemRect)
  607. {
  608. }
  609. private static void DrawItemHighlight(
  610. Graphics g,
  611. Item item,
  612. Rectangle itemRect,
  613. Rectangle highlightRect)
  614. {
  615. Color backFillColor;
  616. Color outlineColor;
  617. if (item.Checked)
  618. {
  619. backFillColor = Color.FromArgb(192, SystemColors.Highlight);
  620. outlineColor = backFillColor;
  621. }
  622. else if (item.Selected)
  623. {
  624. backFillColor = Color.FromArgb(64, SystemColors.HotTrack);
  625. outlineColor = Color.FromArgb(64, SystemColors.HotTrack);
  626. }
  627. else
  628. {
  629. backFillColor = Color.Transparent;
  630. outlineColor = Color.Transparent;
  631. }
  632. using (var backFillBrush = new SolidBrush(backFillColor))
  633. {
  634. g.FillRectangle(backFillBrush, highlightRect);
  635. }
  636. using (var outlinePen = new Pen(outlineColor))
  637. {
  638. g.DrawRectangle(outlinePen, highlightRect.X, highlightRect.Y, highlightRect.Width - 1, highlightRect.Height - 1);
  639. }
  640. }
  641. private static void DrawItemCloseButton(
  642. Graphics g,
  643. Item item,
  644. Rectangle itemRect,
  645. Rectangle closeButtonRect)
  646. {
  647. if (!item.Checked || !item.Selected) return;
  648. const string resourceNamePrefix = "Images.ImageStrip.CloseButton.";
  649. const string resourceNameSuffix = ".png";
  650. string resourceNameInfix = item.CloseRenderState.ToString();
  651. string resourceName = resourceNamePrefix + resourceNameInfix + resourceNameSuffix;
  652. ImageResource imageResource = PdnResources.GetImageResource(resourceName);
  653. Image image = imageResource.Reference;
  654. g.SmoothingMode = SmoothingMode.AntiAlias;
  655. g.InterpolationMode = InterpolationMode.HighQualityBicubic;
  656. g.DrawImage(image, closeButtonRect, new Rectangle(0, 0, image.Width, image.Width), GraphicsUnit.Pixel);
  657. }
  658. private static void DrawItemDirtyOverlay(
  659. Graphics g,
  660. Item item,
  661. Rectangle itemRect,
  662. Rectangle dirtyOverlayRect)
  663. {
  664. Color outerPenColor = Color.White;
  665. Color innerPenColor = Color.Orange;
  666. const int xInset = 2;
  667. int scaledXInset = UI.ScaleWidth(xInset);
  668. const float outerPenWidth = 4.0f;
  669. const float innerPenWidth = 2.0f;
  670. float scaledOuterPenWidth = UI.ScaleWidth(outerPenWidth);
  671. float scaledInnerPenWidth = UI.ScaleWidth(innerPenWidth);
  672. SmoothingMode oldSM = g.SmoothingMode;
  673. g.SmoothingMode = SmoothingMode.AntiAlias;
  674. int left = dirtyOverlayRect.Left + scaledXInset;
  675. int top = dirtyOverlayRect.Top + scaledXInset;
  676. int right = dirtyOverlayRect.Right - scaledXInset;
  677. int bottom = dirtyOverlayRect.Bottom - scaledXInset;
  678. float r = Math.Min((right - left) / 2.0f, (bottom - top) / 2.0f);
  679. var centerPt = new PointF((left + right) / 2.0f, (top + bottom) / 2.0f);
  680. const float twoPiOver5 = (float)(Math.PI * 0.4);
  681. var a = new PointF(centerPt.X + r * (float)Math.Sin(twoPiOver5), centerPt.Y - r * (float)Math.Cos(twoPiOver5));
  682. var b = new PointF(centerPt.X + r * (float)Math.Sin(2 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(2 * twoPiOver5));
  683. var c = new PointF(centerPt.X + r * (float)Math.Sin(3 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(3 * twoPiOver5));
  684. var d = new PointF(centerPt.X + r * (float)Math.Sin(4 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(4 * twoPiOver5));
  685. var e = new PointF(centerPt.X + r * (float)Math.Sin(5 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(5 * twoPiOver5));
  686. var lines =
  687. new[]
  688. {
  689. centerPt, a,
  690. centerPt, b,
  691. centerPt, c,
  692. centerPt, d,
  693. centerPt, e
  694. };
  695. using (var outerPen = new Pen(outerPenColor, scaledOuterPenWidth))
  696. {
  697. for (int i = 0; i < lines.Length; i += 2)
  698. {
  699. g.DrawLine(outerPen, lines[i], lines[i + 1]);
  700. }
  701. }
  702. using (var innerPen = new Pen(innerPenColor, scaledInnerPenWidth))
  703. {
  704. for (int i = 0; i < lines.Length; i += 2)
  705. {
  706. g.DrawLine(innerPen, lines[i], lines[i + 1]);
  707. }
  708. }
  709. g.SmoothingMode = oldSM;
  710. }
  711. private static void DrawItemImageShadow(
  712. Graphics g,
  713. Item item,
  714. Rectangle itemRect,
  715. Rectangle imageRect,
  716. Rectangle imageInsetRect)
  717. {
  718. Rectangle shadowRect = Rectangle.Inflate(imageInsetRect, 1, 1);
  719. Utility.DrawDropShadow1Px(g, shadowRect);
  720. }
  721. private static void DrawItemImage(
  722. Graphics g,
  723. Item item,
  724. Rectangle itemRect,
  725. Rectangle imageRect,
  726. Rectangle imageInsetRect)
  727. {
  728. // Draw the image
  729. if (item.Image != null)
  730. {
  731. g.DrawImage(
  732. item.Image,
  733. imageInsetRect,
  734. new Rectangle(0, 0, item.Image.Width, item.Image.Height),
  735. GraphicsUnit.Pixel);
  736. }
  737. }
  738. private void DrawItem(Graphics g, Item item, Point offset)
  739. {
  740. Rectangle itemRect;
  741. Rectangle imageRect;
  742. Rectangle imageInsetRect;
  743. Rectangle closeButtonRect;
  744. Rectangle dirtyOverlayRect;
  745. MeasureItemPartRectangles(
  746. item,
  747. out itemRect,
  748. out imageRect,
  749. out imageInsetRect,
  750. out closeButtonRect,
  751. out dirtyOverlayRect);
  752. itemRect.X += offset.X;
  753. itemRect.Y += offset.Y;
  754. imageRect.X += offset.X;
  755. imageRect.Y += offset.Y;
  756. imageInsetRect.X += offset.X;
  757. imageInsetRect.Y += offset.Y;
  758. closeButtonRect.X += offset.X;
  759. closeButtonRect.Y += offset.Y;
  760. dirtyOverlayRect.X += offset.X;
  761. dirtyOverlayRect.Y += offset.Y;
  762. DrawItemBackground(g, item, itemRect);
  763. Rectangle highlightRect = itemRect;
  764. DrawItemHighlight(g, item, itemRect, highlightRect);
  765. // Fill background and draw outline
  766. if (_drawShadow)
  767. {
  768. DrawItemImageShadow(g, item, itemRect, imageRect, imageInsetRect);
  769. }
  770. DrawItemImage(g, item, itemRect, imageRect, imageInsetRect);
  771. if (_showCloseButtons)
  772. {
  773. DrawItemCloseButton(g, item, itemRect, closeButtonRect);
  774. }
  775. if (_drawDirtyOverlay && item.Dirty)
  776. {
  777. DrawItemDirtyOverlay(g, item, itemRect, dirtyOverlayRect);
  778. }
  779. }
  780. public Point ClientPointToViewPoint(Point clientPt)
  781. {
  782. int viewX = clientPt.X + _scrollOffset;
  783. return new Point(viewX, clientPt.Y);
  784. }
  785. public Rectangle ClientRectangleToViewRectangle(Rectangle clientRect)
  786. {
  787. Point viewPt = ClientPointToViewPoint(clientRect.Location);
  788. return new Rectangle(viewPt, clientRect.Size);
  789. }
  790. public Point ViewPointToClientPoint(Point viewPt)
  791. {
  792. int clientX = viewPt.X - _scrollOffset;
  793. return new Point(clientX, viewPt.Y);
  794. }
  795. public Rectangle ViewRectangleToClientRectangle(Rectangle viewRect)
  796. {
  797. Point clientPt = ViewPointToClientPoint(viewRect.Location);
  798. return new Rectangle(clientPt, viewRect.Size);
  799. }
  800. private Point ViewPointToItemPoint(int itemIndex, Point viewPt)
  801. {
  802. Rectangle itemRect = ItemIndexToItemViewRectangle(itemIndex);
  803. var itemPt = new Point(viewPt.X - itemRect.X, viewPt.Y);
  804. return itemPt;
  805. }
  806. private Rectangle ItemIndexToItemViewRectangle(int itemIndex)
  807. {
  808. Size itemSize = ItemSize;
  809. return new Rectangle(itemSize.Width * itemIndex, itemSize.Height, itemSize.Width, itemSize.Height);
  810. }
  811. public int ViewPointToItemIndex(Point viewPt)
  812. {
  813. if (!ViewRectangle.Contains(viewPt))
  814. {
  815. return -1;
  816. }
  817. Size itemSize = ItemSize;
  818. int index = viewPt.X / itemSize.Width;
  819. return index;
  820. }
  821. private void MeasureItemPartRectangles(
  822. out Rectangle itemRect,
  823. out Rectangle imageRect)
  824. {
  825. itemRect = new Rectangle(
  826. 0,
  827. 0,
  828. ClientSize.Height,
  829. ClientSize.Height);
  830. imageRect = new Rectangle(
  831. itemRect.Left,
  832. itemRect.Top,
  833. itemRect.Width,
  834. itemRect.Width);
  835. }
  836. private void MeasureItemPartRectangles(
  837. Item item,
  838. out Rectangle itemRect,
  839. out Rectangle imageRect,
  840. out Rectangle imageInsetRect,
  841. out Rectangle closeButtonRect,
  842. out Rectangle dirtyOverlayRect)
  843. {
  844. MeasureItemPartRectangles(out itemRect, out imageRect);
  845. var imageInsetRectMax = new Rectangle(
  846. imageRect.Left + ImagePadding,
  847. imageRect.Top + ImagePadding,
  848. imageRect.Width - ImagePadding * 2,
  849. imageRect.Height - ImagePadding * 2);
  850. Size imageInsetSize = item.Image == null ? imageRect.Size : Utility.ComputeThumbnailSize(item.Image.Size, imageInsetRectMax.Width);
  851. int scaledCloseButtonLength = UI.ScaleWidth(CloseButtonLength);
  852. int scaledCloseButtonPadding = UI.ScaleWidth(CloseButtonPadding);
  853. imageInsetRect = new Rectangle(
  854. imageInsetRectMax.Left + (imageInsetRectMax.Width - imageInsetSize.Width) / 2,
  855. imageInsetRectMax.Bottom - imageInsetSize.Height,
  856. imageInsetSize.Width,
  857. imageInsetSize.Height);
  858. closeButtonRect = new Rectangle(
  859. imageInsetRectMax.Right - scaledCloseButtonLength - scaledCloseButtonPadding,
  860. imageInsetRectMax.Top + scaledCloseButtonPadding,
  861. scaledCloseButtonLength,
  862. scaledCloseButtonLength);
  863. dirtyOverlayRect = new Rectangle(
  864. imageInsetRectMax.Left + scaledCloseButtonPadding,
  865. imageInsetRectMax.Top + scaledCloseButtonPadding,
  866. scaledCloseButtonLength,
  867. scaledCloseButtonLength);
  868. }
  869. private ItemPart ItemPointToItemPart(Item item, Point pt)
  870. {
  871. Rectangle itemRect;
  872. Rectangle imageRect;
  873. Rectangle imageInsetRect;
  874. Rectangle closeButtonRect;
  875. Rectangle dirtyOverlayRect;
  876. MeasureItemPartRectangles(
  877. item,
  878. out itemRect,
  879. out imageRect,
  880. out imageInsetRect,
  881. out closeButtonRect,
  882. out dirtyOverlayRect);
  883. if (closeButtonRect.Contains(pt))
  884. {
  885. return ItemPart.CloseButton;
  886. }
  887. return imageRect.Contains(pt) ? ItemPart.Image : ItemPart.None;
  888. }
  889. private Rectangle ItemIndexToClientRect(int itemIndex)
  890. {
  891. Size itemSize = ItemSize;
  892. var clientRect = new Rectangle(
  893. itemSize.Width * itemIndex,
  894. 0,
  895. itemSize.Width,
  896. itemSize.Height);
  897. return clientRect;
  898. }
  899. private void CalculateVisibleScrollOffsets(
  900. int itemIndex,
  901. out int minOffset,
  902. out int maxOffset,
  903. out int minFullyShownOffset,
  904. out int maxFullyShownOffset)
  905. {
  906. Rectangle itemClientRect = ItemIndexToClientRect(itemIndex);
  907. minOffset = itemClientRect.Left + 1 - ClientSize.Width;
  908. maxOffset = itemClientRect.Right - 1;
  909. minFullyShownOffset = itemClientRect.Right - ClientSize.Width;
  910. maxFullyShownOffset = itemClientRect.Left;
  911. if (LeftScrollButton.Visible)
  912. {
  913. maxOffset -= LeftScrollButton.Width;
  914. maxFullyShownOffset -= LeftScrollButton.Width;
  915. }
  916. if (!RightScrollButton.Visible) return;
  917. minOffset += RightScrollButton.Width;
  918. minFullyShownOffset += RightScrollButton.Width;
  919. }
  920. public Rectangle ScrolledViewRect
  921. {
  922. get
  923. {
  924. return new Rectangle(_scrollOffset, 0, ClientSize.Width, ClientSize.Height);
  925. }
  926. }
  927. public bool IsItemVisible(int index)
  928. {
  929. Rectangle itemRect = ItemIndexToClientRect(index);
  930. Rectangle intersect = Rectangle.Intersect(itemRect, ScrolledViewRect);
  931. return (intersect.Width > 0 || intersect.Height > 0);
  932. }
  933. public bool IsItemFullyVisible(int index)
  934. {
  935. Rectangle itemRect = ItemIndexToClientRect(index);
  936. Rectangle svRect = ScrolledViewRect;
  937. if (LeftScrollButton.Visible)
  938. {
  939. svRect.X += LeftScrollButton.Width;
  940. svRect.Width -= LeftScrollButton.Width;
  941. }
  942. if (RightScrollButton.Visible)
  943. {
  944. svRect.Width -= RightScrollButton.Width;
  945. }
  946. Rectangle intersect = Rectangle.Intersect(itemRect, svRect);
  947. return (intersect == itemRect);
  948. }
  949. public void EnsureItemFullyVisible(Item item)
  950. {
  951. int index = _items.IndexOf(item);
  952. EnsureItemFullyVisible(index);
  953. }
  954. public void EnsureItemFullyVisible(int index)
  955. {
  956. if (IsItemFullyVisible(index))
  957. {
  958. return;
  959. }
  960. int minOffset;
  961. int maxOffset;
  962. int minFullyShownOffset;
  963. int maxFullyShownOffset;
  964. CalculateVisibleScrollOffsets(index, out minOffset, out maxOffset,
  965. out minFullyShownOffset, out maxFullyShownOffset);
  966. // Pick the offset that moves the image the fewest number of pixels
  967. int oldOffset = _scrollOffset;
  968. int dxMin = Math.Abs(oldOffset - minFullyShownOffset);
  969. int dxMax = Math.Abs(oldOffset - maxFullyShownOffset);
  970. ScrollOffset = dxMin <= dxMax ? minFullyShownOffset : maxFullyShownOffset;
  971. }
  972. protected override void OnPaint(PaintEventArgs e)
  973. {
  974. if (UI.IsControlPaintingEnabled(this))
  975. {
  976. Size itemSize = ItemSize;
  977. var firstItemRect = new Rectangle(-_scrollOffset, 0, itemSize.Width, itemSize.Height);
  978. for (int i = 0; i < _items.Count; ++i)
  979. {
  980. if (!IsItemVisible(i)) continue;
  981. var itemOffset = new Point(firstItemRect.X + itemSize.Width * i, firstItemRect.Y);
  982. DrawItem(e.Graphics, _items[i], itemOffset);
  983. }
  984. }
  985. base.OnPaint(e);
  986. }
  987. protected override void OnMouseDown(MouseEventArgs e)
  988. {
  989. if (_mouseDownButton == MouseButtons.None)
  990. {
  991. var clientPt = new Point(e.X, e.Y);
  992. Point viewPt = ClientPointToViewPoint(clientPt);
  993. int itemIndex = ViewPointToItemIndex(viewPt);
  994. if (itemIndex >= 0 && itemIndex < _items.Count)
  995. {
  996. Item item = _items[itemIndex];
  997. Point itemPt = ViewPointToItemPoint(itemIndex, viewPt);
  998. ItemPart itemPart = ItemPointToItemPart(item, itemPt);
  999. if (itemPart == ItemPart.Image)
  1000. {
  1001. OnItemClicked(item, itemPart, e.Button);
  1002. _mouseDownApplyRendering = false;
  1003. _mouseOverIndex = itemIndex;
  1004. _mouseOverItemPart = itemPart;
  1005. _mouseOverApplyRendering = true;
  1006. }
  1007. else
  1008. {
  1009. _mouseDownIndex = itemIndex;
  1010. _mouseDownItemPart = itemPart;
  1011. _mouseDownButton = e.Button;
  1012. _mouseDownApplyRendering = true;
  1013. _mouseOverApplyRendering = false;
  1014. }
  1015. MouseStatesToItemStates();
  1016. Refresh();
  1017. }
  1018. }
  1019. base.OnMouseDown(e);
  1020. }
  1021. protected override void OnMouseMove(MouseEventArgs e)
  1022. {
  1023. GetFocus();
  1024. var clientPt = new Point(e.X, e.Y);
  1025. if (clientPt != _lastMouseMovePt)
  1026. {
  1027. Point viewPt = ClientPointToViewPoint(clientPt);
  1028. int itemIndex = ViewPointToItemIndex(viewPt);
  1029. if (_mouseDownButton == MouseButtons.None)
  1030. {
  1031. if (itemIndex >= 0 && itemIndex < _items.Count)
  1032. {
  1033. Item item = _items[itemIndex];
  1034. Point itemPt = ViewPointToItemPoint(itemIndex, viewPt);
  1035. ItemPart itemPart = ItemPointToItemPart(item, itemPt);
  1036. _mouseOverIndex = itemIndex;
  1037. _mouseOverItemPart = itemPart;
  1038. _mouseOverApplyRendering = true;
  1039. }
  1040. else
  1041. {
  1042. _mouseOverApplyRendering = false;
  1043. }
  1044. }
  1045. else
  1046. {
  1047. _mouseOverApplyRendering = false;
  1048. if (itemIndex != _mouseDownIndex)
  1049. {
  1050. _mouseDownApplyRendering = false;
  1051. }
  1052. else if (itemIndex < 0 || itemIndex >= _items.Count)
  1053. {
  1054. _mouseDownApplyRendering = false;
  1055. }
  1056. else
  1057. {
  1058. Item item = Items[itemIndex];
  1059. Point itemPt = ViewPointToItemPoint(itemIndex, viewPt);
  1060. ItemPart itemPart = ItemPointToItemPart(item, itemPt);
  1061. if (itemPart != _mouseDownItemPart)
  1062. {
  1063. _mouseDownApplyRendering = false;
  1064. }
  1065. }
  1066. }
  1067. MouseStatesToItemStates();
  1068. Refresh();
  1069. }
  1070. _lastMouseMovePt = clientPt;
  1071. base.OnMouseMove(e);
  1072. }
  1073. protected override void OnMouseUp(MouseEventArgs e)
  1074. {
  1075. bool raisedClickEvent = false;
  1076. if (_mouseDownButton == e.Button)
  1077. {
  1078. var clientPt = new Point(e.X, e.Y);
  1079. Point viewPt = ClientPointToViewPoint(clientPt);
  1080. int itemIndex = ViewPointToItemIndex(viewPt);
  1081. if (itemIndex >= 0 && itemIndex < _items.Count)
  1082. {
  1083. Item item = _items[itemIndex];
  1084. Point itemPt = ViewPointToItemPoint(itemIndex, viewPt);
  1085. ItemPart itemPart = ItemPointToItemPart(item, itemPt);
  1086. if (itemIndex == _mouseDownIndex && itemPart == _mouseDownItemPart)
  1087. {
  1088. if (itemPart == ItemPart.CloseButton && !item.Checked)
  1089. {
  1090. // Can only close 'checked' images, just like how tab switching+closing works in IE7
  1091. itemPart = ItemPart.Image;
  1092. }
  1093. OnItemClicked(item, itemPart, _mouseDownButton);
  1094. raisedClickEvent = true;
  1095. }
  1096. _mouseOverApplyRendering = true;
  1097. _mouseOverItemPart = itemPart;
  1098. _mouseOverIndex = itemIndex;
  1099. }
  1100. _mouseDownApplyRendering = false;
  1101. _mouseDownButton = MouseButtons.None;
  1102. MouseStatesToItemStates();
  1103. Refresh();
  1104. }
  1105. if (raisedClickEvent)
  1106. {
  1107. ForceMouseMove();
  1108. }
  1109. base.OnMouseUp(e);
  1110. }
  1111. protected override void OnMouseWheel(MouseEventArgs e)
  1112. {
  1113. float count = (float)e.Delta / SystemInformation.MouseWheelScrollDelta;
  1114. var pixels = (int)(count * ItemSize.Width);
  1115. int newSO = ScrollOffset - pixels;
  1116. ScrollOffset = newSO;
  1117. ForceMouseMove();
  1118. base.OnMouseWheel(e);
  1119. }
  1120. private void ForceMouseMove()
  1121. {
  1122. Point clientPt = PointToClient(MousePosition);
  1123. _lastMouseMovePt = new Point(_lastMouseMovePt.X + 1, _lastMouseMovePt.Y + 1);
  1124. var me = new MouseEventArgs(MouseButtons.None, 0, clientPt.X, clientPt.Y, 0);
  1125. OnMouseMove(me);
  1126. }
  1127. protected override void OnMouseEnter(EventArgs e)
  1128. {
  1129. GetFocus();
  1130. base.OnMouseEnter(e);
  1131. }
  1132. private void GetFocus()
  1133. {
  1134. if (_managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive)
  1135. {
  1136. Focus();
  1137. }
  1138. }
  1139. protected override void OnMouseLeave(EventArgs e)
  1140. {
  1141. _mouseDownApplyRendering = false;
  1142. _mouseOverApplyRendering = false;
  1143. MouseStatesToItemStates();
  1144. Refresh();
  1145. if (_managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive)
  1146. {
  1147. OnRelinquishFocus();
  1148. }
  1149. base.OnMouseLeave(e);
  1150. }
  1151. }
  1152. }