ColorPicker.16.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. namespace Terminal.Gui.Views;
  2. /// <summary>A sinple color picker that supports the legacy 16 ANSI colors</summary>
  3. public class ColorPicker16 : View
  4. {
  5. /// <summary>Initializes a new instance of <see cref="ColorPicker16"/>.</summary>
  6. public ColorPicker16 () { SetInitialProperties (); }
  7. /// <summary>Columns of color boxes</summary>
  8. private const int COLS = 8;
  9. /// <summary>Rows of color boxes</summary>
  10. private const int ROWS = 2;
  11. private int _boxHeight = 2;
  12. private int _boxWidth = 4;
  13. private int _selectColorIndex = (int)Color.Black;
  14. /// <summary>Height of a color box</summary>
  15. public int BoxHeight
  16. {
  17. get => _boxHeight;
  18. set
  19. {
  20. if (_boxHeight != value)
  21. {
  22. _boxHeight = value;
  23. Width = Dim.Auto (minimumContentDim: _boxWidth * COLS);
  24. Height = Dim.Auto (minimumContentDim: _boxHeight * ROWS);
  25. SetContentSize (new (_boxWidth * COLS, _boxHeight * ROWS));
  26. SetNeedsLayout ();
  27. }
  28. }
  29. }
  30. /// <summary>Width of a color box</summary>
  31. public int BoxWidth
  32. {
  33. get => _boxWidth;
  34. set
  35. {
  36. if (_boxWidth != value)
  37. {
  38. _boxWidth = value;
  39. Width = Dim.Auto (minimumContentDim: _boxWidth * COLS);
  40. Height = Dim.Auto (minimumContentDim: _boxHeight * ROWS);
  41. SetContentSize (new (_boxWidth * COLS, _boxHeight * ROWS));
  42. SetNeedsLayout ();
  43. }
  44. }
  45. }
  46. /// <summary>Fired when a color is picked.</summary>
  47. public event EventHandler<ResultEventArgs<Color>>? ColorChanged;
  48. /// <summary>Cursor for the selected color.</summary>
  49. public Point Cursor
  50. {
  51. get => new (_selectColorIndex % COLS, _selectColorIndex / COLS);
  52. set
  53. {
  54. int colorIndex = value.Y * COLS + value.X;
  55. SelectedColor = (ColorName16)colorIndex;
  56. }
  57. }
  58. /// <summary>Moves the selected item index to the next row.</summary>
  59. /// <returns></returns>
  60. private bool MoveDown (ICommandContext? commandContext)
  61. {
  62. if (RaiseActivating (commandContext) == true)
  63. {
  64. return true;
  65. }
  66. if (Cursor.Y < ROWS - 1)
  67. {
  68. SelectedColor += COLS;
  69. }
  70. return true;
  71. }
  72. /// <summary>Moves the selected item index to the previous column.</summary>
  73. /// <returns></returns>
  74. private bool MoveLeft (ICommandContext? commandContext)
  75. {
  76. if (RaiseActivating (commandContext) == true)
  77. {
  78. return true;
  79. }
  80. if (Cursor.X > 0)
  81. {
  82. SelectedColor--;
  83. }
  84. return true;
  85. }
  86. /// <summary>Moves the selected item index to the next column.</summary>
  87. /// <returns></returns>
  88. private bool MoveRight (ICommandContext? commandContext)
  89. {
  90. if (RaiseActivating (commandContext) == true)
  91. {
  92. return true;
  93. }
  94. if (Cursor.X < COLS - 1)
  95. {
  96. SelectedColor++;
  97. }
  98. return true;
  99. }
  100. /// <summary>Moves the selected item index to the previous row.</summary>
  101. /// <returns></returns>
  102. private bool MoveUp (ICommandContext? commandContext)
  103. {
  104. if (RaiseActivating (commandContext) == true)
  105. {
  106. return true;
  107. }
  108. if (Cursor.Y > 0)
  109. {
  110. SelectedColor -= COLS;
  111. }
  112. return true;
  113. }
  114. ///<inheritdoc/>
  115. protected override bool OnDrawingContent (DrawContext? context)
  116. {
  117. SetAttribute (HasFocus ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal));
  118. var colorIndex = 0;
  119. for (var y = 0; y < Math.Max (2, Viewport.Height / BoxHeight); y++)
  120. {
  121. for (var x = 0; x < Math.Max (8, Viewport.Width / BoxWidth); x++)
  122. {
  123. int foregroundColorIndex = y == 0 ? colorIndex + COLS : colorIndex - COLS;
  124. if (foregroundColorIndex > 15 || colorIndex > 15)
  125. {
  126. continue;
  127. }
  128. if (Enabled)
  129. {
  130. SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex));
  131. }
  132. else
  133. {
  134. SetAttribute (new ((ColorName16)foregroundColorIndex, ((Color)(ColorName16)colorIndex).GetDimColor (), TextStyle.Faint));
  135. }
  136. bool selected = x == Cursor.X && y == Cursor.Y;
  137. DrawColorBox (x, y, selected);
  138. colorIndex++;
  139. }
  140. }
  141. return true;
  142. }
  143. /// <summary>Selected color.</summary>
  144. public ColorName16 SelectedColor
  145. {
  146. get => (ColorName16)_selectColorIndex;
  147. set
  148. {
  149. if (value == (ColorName16)_selectColorIndex)
  150. {
  151. return;
  152. }
  153. _selectColorIndex = (int)value;
  154. ColorChanged?.Invoke (
  155. this,
  156. new (value)
  157. );
  158. SetNeedsDraw ();
  159. }
  160. }
  161. /// <summary>Add the commands.</summary>
  162. private void AddCommands ()
  163. {
  164. AddCommand (Command.Left, (ctx) => MoveLeft (ctx));
  165. AddCommand (Command.Right, (ctx) => MoveRight (ctx));
  166. AddCommand (Command.Up, (ctx) => MoveUp (ctx));
  167. AddCommand (Command.Down, (ctx) => MoveDown (ctx));
  168. AddCommand (Command.Activate, (ctx) =>
  169. {
  170. var set = false;
  171. if (ctx is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } } mouseCommandContext)
  172. {
  173. Cursor = new (mouseCommandContext.Binding.MouseEventArgs.Position.X / _boxWidth, mouseCommandContext.Binding.MouseEventArgs.Position.Y / _boxHeight);
  174. set = true;
  175. }
  176. return RaiseAccepting (ctx) == true || set;
  177. });
  178. }
  179. /// <summary>Add the KeyBindings.</summary>
  180. private void AddKeyBindings ()
  181. {
  182. KeyBindings.Add (Key.CursorLeft, Command.Left);
  183. KeyBindings.Add (Key.CursorRight, Command.Right);
  184. KeyBindings.Add (Key.CursorUp, Command.Up);
  185. KeyBindings.Add (Key.CursorDown, Command.Down);
  186. }
  187. // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
  188. /// <summary>Draw a box for one color.</summary>
  189. /// <param name="x">X location.</param>
  190. /// <param name="y">Y location</param>
  191. /// <param name="selected"></param>
  192. private void DrawColorBox (int x, int y, bool selected)
  193. {
  194. for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++)
  195. {
  196. for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
  197. {
  198. AddRune (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY, (Rune)' ');
  199. }
  200. }
  201. if (selected)
  202. {
  203. DrawFocusRect (new (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
  204. }
  205. }
  206. private void DrawFocusRect (Rectangle rect)
  207. {
  208. var lc = new LineCanvas ();
  209. if (rect.Width == 1)
  210. {
  211. lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
  212. }
  213. else if (rect.Height == 1)
  214. {
  215. lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
  216. }
  217. else
  218. {
  219. lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
  220. lc.AddLine (
  221. rect.Location with { Y = rect.Location.Y + rect.Height - 1 },
  222. rect.Width,
  223. Orientation.Horizontal,
  224. LineStyle.Dotted
  225. );
  226. lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
  227. lc.AddLine (
  228. rect.Location with { X = rect.Location.X + rect.Width - 1 },
  229. rect.Height,
  230. Orientation.Vertical,
  231. LineStyle.Dotted
  232. );
  233. }
  234. foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
  235. {
  236. AddRune (p.Key.X, p.Key.Y, p.Value);
  237. }
  238. }
  239. private void SetInitialProperties ()
  240. {
  241. HighlightStates = ViewBase.MouseState.PressedOutside | ViewBase.MouseState.Pressed;
  242. CanFocus = true;
  243. AddCommands ();
  244. AddKeyBindings ();
  245. Width = Dim.Auto (minimumContentDim: _boxWidth * COLS);
  246. Height = Dim.Auto (minimumContentDim: _boxHeight * ROWS);
  247. SetContentSize (new (_boxWidth * COLS, _boxHeight * ROWS));
  248. }
  249. }