ColorPickerTests.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. #nullable enable
  2. namespace ViewsTests;
  3. /// <summary>
  4. /// Pure unit tests for <see cref="ColorPicker"/> that don't require Application.Driver or View context.
  5. /// These tests can run in parallel without interference.
  6. /// </summary>
  7. public class ColorPickerTests
  8. {
  9. [Fact]
  10. public void ChangedEvent_Fires ()
  11. {
  12. Color newColor = default;
  13. var count = 0;
  14. var cp = new ColorPicker ();
  15. cp.ColorChanged += (s, e) =>
  16. {
  17. count++;
  18. newColor = e.Result;
  19. Assert.Equal (cp.SelectedColor, e.Result);
  20. };
  21. cp.SelectedColor = new (1, 2, 3);
  22. Assert.Equal (1, count);
  23. Assert.Equal (new (1, 2, 3), newColor);
  24. cp.SelectedColor = new (2, 3, 4);
  25. Assert.Equal (2, count);
  26. Assert.Equal (new (2, 3, 4), newColor);
  27. // Set to same value
  28. cp.SelectedColor = new (2, 3, 4);
  29. // Should have no effect
  30. Assert.Equal (2, count);
  31. }
  32. [Fact]
  33. public void ChangeValueOnUI_UpdatesAllUIElements ()
  34. {
  35. ColorPicker cp = GetColorPicker (ColorModel.RGB, true);
  36. var otherView = new View { CanFocus = true };
  37. cp.App!.TopRunnableView?.Add (otherView); // thi sets focus to otherView
  38. Assert.True (otherView.HasFocus);
  39. cp.SetFocus ();
  40. Assert.False (otherView.HasFocus);
  41. cp.Draw (); // Draw is needed to update TrianglePosition
  42. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  43. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  44. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  45. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  46. TextField rTextField = GetTextField (cp, ColorPickerPart.Bar1);
  47. TextField gTextField = GetTextField (cp, ColorPickerPart.Bar2);
  48. TextField bTextField = GetTextField (cp, ColorPickerPart.Bar3);
  49. Assert.Equal ("R:", r.Text);
  50. Assert.Equal (2, r.TrianglePosition);
  51. Assert.Equal ("0", rTextField.Text);
  52. Assert.Equal ("G:", g.Text);
  53. Assert.Equal (2, g.TrianglePosition);
  54. Assert.Equal ("0", gTextField.Text);
  55. Assert.Equal ("B:", b.Text);
  56. Assert.Equal (2, b.TrianglePosition);
  57. Assert.Equal ("0", bTextField.Text);
  58. Assert.Equal ("#000000", hex.Text);
  59. // Change value using text field
  60. TextField rBarTextField = cp.SubViews.OfType<TextField> ().First (tf => tf.Text == "0");
  61. rBarTextField.SetFocus ();
  62. rBarTextField.Text = "128";
  63. otherView.SetFocus ();
  64. Assert.True (otherView.HasFocus);
  65. cp.Draw (); // Draw is needed to update TrianglePosition
  66. Assert.Equal ("R:", r.Text);
  67. Assert.Equal (9, r.TrianglePosition);
  68. Assert.Equal ("128", rTextField.Text);
  69. Assert.Equal ("G:", g.Text);
  70. Assert.Equal (2, g.TrianglePosition);
  71. Assert.Equal ("0", gTextField.Text);
  72. Assert.Equal ("B:", b.Text);
  73. Assert.Equal (2, b.TrianglePosition);
  74. Assert.Equal ("0", bTextField.Text);
  75. Assert.Equal ("#800000", hex.Text);
  76. cp.App?.Dispose ();
  77. }
  78. [Fact]
  79. public void ClickingAtEndOfBar_SetsMaxValue ()
  80. {
  81. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  82. cp.Draw (); // Draw is needed to update TrianglePosition
  83. // Click at the end of the Red bar
  84. cp.Focused!.RaiseMouseEvent (
  85. new ()
  86. {
  87. Flags = MouseFlags.Button1Pressed,
  88. Position = new (19, 0) // Assuming 0-based indexing
  89. });
  90. cp.Draw (); // Draw is needed to update TrianglePosition
  91. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  92. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  93. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  94. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  95. Assert.Equal ("R:", r.Text);
  96. Assert.Equal (19, r.TrianglePosition);
  97. Assert.Equal ("G:", g.Text);
  98. Assert.Equal (2, g.TrianglePosition);
  99. Assert.Equal ("B:", b.Text);
  100. Assert.Equal (2, b.TrianglePosition);
  101. Assert.Equal ("#FF0000", hex.Text);
  102. cp.App?.Dispose ();
  103. }
  104. [Fact]
  105. public void ClickingBeyondBar_ChangesToMaxValue ()
  106. {
  107. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  108. cp.Draw (); // Draw is needed to update TrianglePosition
  109. // Click beyond the bar
  110. cp.Focused!.RaiseMouseEvent (
  111. new ()
  112. {
  113. Flags = MouseFlags.Button1Pressed,
  114. Position = new (21, 0) // Beyond the bar
  115. });
  116. cp.Draw (); // Draw is needed to update TrianglePosition
  117. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  118. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  119. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  120. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  121. Assert.Equal ("R:", r.Text);
  122. Assert.Equal (19, r.TrianglePosition);
  123. Assert.Equal ("G:", g.Text);
  124. Assert.Equal (2, g.TrianglePosition);
  125. Assert.Equal ("B:", b.Text);
  126. Assert.Equal (2, b.TrianglePosition);
  127. Assert.Equal ("#FF0000", hex.Text);
  128. cp.App?.Dispose ();
  129. }
  130. [Fact]
  131. public void ClickingDifferentBars_ChangesFocus ()
  132. {
  133. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  134. cp.Draw (); // Draw is needed to update TrianglePosition
  135. // Click on Green bar
  136. cp.App!.Mouse.RaiseMouseEvent (
  137. new ()
  138. {
  139. Flags = MouseFlags.Button1Pressed,
  140. ScreenPosition = new (0, 1)
  141. });
  142. //cp.SubViews.OfType<GBar> ()
  143. // .Single ()
  144. // .OnMouseEvent (
  145. // new ()
  146. // {
  147. // Flags = MouseFlags.Button1Pressed,
  148. // Position = new (0, 1)
  149. // });
  150. cp.Draw (); // Draw is needed to update TrianglePosition
  151. Assert.IsAssignableFrom<GBar> (cp.Focused);
  152. // Click on Blue bar
  153. cp.App!.Mouse.RaiseMouseEvent (
  154. new ()
  155. {
  156. Flags = MouseFlags.Button1Pressed,
  157. ScreenPosition = new (0, 2)
  158. });
  159. //cp.SubViews.OfType<BBar> ()
  160. // .Single ()
  161. // .OnMouseEvent (
  162. // new ()
  163. // {
  164. // Flags = MouseFlags.Button1Pressed,
  165. // Position = new (0, 2)
  166. // });
  167. cp.Draw (); // Draw is needed to update TrianglePosition
  168. Assert.IsAssignableFrom<BBar> (cp.Focused);
  169. cp.App?.Dispose ();
  170. }
  171. [Fact]
  172. public void Construct_DefaultValue ()
  173. {
  174. ColorPicker cp = GetColorPicker (ColorModel.HSV, false);
  175. // Should be only a single text field (Hex) because ShowTextFields is false
  176. Assert.Single (cp.SubViews.OfType<TextField> ());
  177. cp.Draw (); // Draw is needed to update TrianglePosition
  178. // All bars should be at 0 with the triangle at 0 (+2 because of "H:", "S:" etc)
  179. ColorBar h = GetColorBar (cp, ColorPickerPart.Bar1);
  180. Assert.Equal ("H:", h.Text);
  181. Assert.Equal (2, h.TrianglePosition);
  182. Assert.IsType<HueBar> (h);
  183. ColorBar s = GetColorBar (cp, ColorPickerPart.Bar2);
  184. Assert.Equal ("S:", s.Text);
  185. Assert.Equal (2, s.TrianglePosition);
  186. Assert.IsType<SaturationBar> (s);
  187. ColorBar v = GetColorBar (cp, ColorPickerPart.Bar3);
  188. Assert.Equal ("V:", v.Text);
  189. Assert.Equal (2, v.TrianglePosition);
  190. Assert.IsType<ValueBar> (v);
  191. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  192. Assert.Equal ("#000000", hex.Text);
  193. cp.App?.Dispose ();
  194. }
  195. [Fact]
  196. public void DisposesOldViews_OnModelChange ()
  197. {
  198. ColorPicker cp = GetColorPicker (ColorModel.HSL, true);
  199. ColorBar b1 = GetColorBar (cp, ColorPickerPart.Bar1);
  200. ColorBar b2 = GetColorBar (cp, ColorPickerPart.Bar2);
  201. ColorBar b3 = GetColorBar (cp, ColorPickerPart.Bar3);
  202. TextField tf1 = GetTextField (cp, ColorPickerPart.Bar1);
  203. TextField tf2 = GetTextField (cp, ColorPickerPart.Bar2);
  204. TextField tf3 = GetTextField (cp, ColorPickerPart.Bar3);
  205. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  206. #if DEBUG_IDISPOSABLE
  207. Assert.All (new View [] { b1, b2, b3, tf1, tf2, tf3, hex }, b => Assert.False (b.WasDisposed));
  208. #endif
  209. cp.Style.ColorModel = ColorModel.RGB;
  210. cp.ApplyStyleChanges ();
  211. ColorBar b1After = GetColorBar (cp, ColorPickerPart.Bar1);
  212. ColorBar b2After = GetColorBar (cp, ColorPickerPart.Bar2);
  213. ColorBar b3After = GetColorBar (cp, ColorPickerPart.Bar3);
  214. TextField tf1After = GetTextField (cp, ColorPickerPart.Bar1);
  215. TextField tf2After = GetTextField (cp, ColorPickerPart.Bar2);
  216. TextField tf3After = GetTextField (cp, ColorPickerPart.Bar3);
  217. TextField hexAfter = GetTextField (cp, ColorPickerPart.Hex);
  218. // Old bars should be disposed
  219. #if DEBUG_IDISPOSABLE
  220. Assert.All (new View [] { b1, b2, b3, tf1, tf2, tf3, hex }, b => Assert.True (b.WasDisposed));
  221. #endif
  222. Assert.NotSame (hex, hexAfter);
  223. Assert.NotSame (b1, b1After);
  224. Assert.NotSame (b2, b2After);
  225. Assert.NotSame (b3, b3After);
  226. Assert.NotSame (tf1, tf1After);
  227. Assert.NotSame (tf2, tf2After);
  228. Assert.NotSame (tf3, tf3After);
  229. cp.App?.Dispose ();
  230. }
  231. [Fact]
  232. public void EnterHexFor_ColorName ()
  233. {
  234. ColorPicker cp = GetColorPicker (ColorModel.RGB, true, true);
  235. cp.Draw (); // Draw is needed to update TrianglePosition
  236. TextField name = GetTextField (cp, ColorPickerPart.ColorName);
  237. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  238. hex.SetFocus ();
  239. Assert.True (hex.HasFocus);
  240. Assert.Same (hex, cp.Focused);
  241. hex.Text = "";
  242. name.Text = "";
  243. Assert.Empty (hex.Text);
  244. Assert.Empty (name.Text);
  245. cp.App!.Keyboard.RaiseKeyDownEvent ('#');
  246. Assert.Empty (name.Text);
  247. //7FFFD4
  248. Assert.Equal ("#", hex.Text);
  249. cp.App!.Keyboard.RaiseKeyDownEvent ('7');
  250. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  251. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  252. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  253. cp.App!.Keyboard.RaiseKeyDownEvent ('D');
  254. Assert.Empty (name.Text);
  255. cp.App!.Keyboard.RaiseKeyDownEvent ('4');
  256. Assert.True (hex.HasFocus);
  257. // Tab out of the hex field - should wrap to first focusable subview
  258. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Tab);
  259. Assert.False (hex.HasFocus);
  260. Assert.NotSame (hex, cp.Focused);
  261. // Color name should be recognised as a known string and populated
  262. Assert.Equal ("#7FFFD4", hex.Text);
  263. Assert.Equal ("Aquamarine", name.Text);
  264. cp.App?.Dispose ();
  265. }
  266. /// <summary>
  267. /// In this version we use the Enter button to accept the typed text instead
  268. /// of tabbing to the next view.
  269. /// </summary>
  270. [Fact]
  271. public void EnterHexFor_ColorName_AcceptVariation ()
  272. {
  273. ColorPicker cp = GetColorPicker (ColorModel.RGB, true, true);
  274. cp.Draw (); // Draw is needed to update TrianglePosition
  275. TextField name = GetTextField (cp, ColorPickerPart.ColorName);
  276. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  277. hex.SetFocus ();
  278. Assert.True (hex.HasFocus);
  279. Assert.Same (hex, cp.Focused);
  280. hex.Text = "";
  281. name.Text = "";
  282. Assert.Empty (hex.Text);
  283. Assert.Empty (name.Text);
  284. cp.App!.Keyboard.RaiseKeyDownEvent ('#');
  285. Assert.Empty (name.Text);
  286. //7FFFD4
  287. Assert.Equal ("#", hex.Text);
  288. cp.App!.Keyboard.RaiseKeyDownEvent ('7');
  289. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  290. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  291. cp.App!.Keyboard.RaiseKeyDownEvent ('F');
  292. cp.App!.Keyboard.RaiseKeyDownEvent ('D');
  293. Assert.Empty (name.Text);
  294. cp.App!.Keyboard.RaiseKeyDownEvent ('4');
  295. Assert.True (hex.HasFocus);
  296. // Should stay in the hex field (because accept not tab)
  297. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Enter);
  298. Assert.True (hex.HasFocus);
  299. Assert.Same (hex, cp.Focused);
  300. // But still, Color name should be recognised as a known string and populated
  301. Assert.Equal ("#7FFFD4", hex.Text);
  302. Assert.Equal ("Aquamarine", name.Text);
  303. cp.App?.Dispose ();
  304. }
  305. [Fact]
  306. public void InvalidHexInput_DoesNotChangeColor ()
  307. {
  308. ColorPicker cp = GetColorPicker (ColorModel.RGB, true);
  309. cp.Draw (); // Draw is needed to update TrianglePosition
  310. // Enter invalid hex value
  311. TextField hexField = cp.SubViews.OfType<TextField> ().First (tf => tf.Text == "#000000");
  312. hexField.SetFocus ();
  313. hexField.Text = "#ZZZZZZ";
  314. Assert.True (hexField.HasFocus);
  315. Assert.Equal ("#ZZZZZZ", hexField.Text);
  316. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  317. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  318. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  319. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  320. Assert.Equal ("#ZZZZZZ", hex.Text);
  321. // Advance away from hexField to cause validation
  322. cp.AdvanceFocus (NavigationDirection.Forward, null);
  323. cp.Draw (); // Draw is needed to update TrianglePosition
  324. Assert.Equal ("R:", r.Text);
  325. Assert.Equal (2, r.TrianglePosition);
  326. Assert.Equal ("G:", g.Text);
  327. Assert.Equal (2, g.TrianglePosition);
  328. Assert.Equal ("B:", b.Text);
  329. Assert.Equal (2, b.TrianglePosition);
  330. Assert.Equal ("#000000", hex.Text);
  331. cp.App?.Dispose ();
  332. }
  333. [Fact]
  334. public void RGB_KeyboardNavigation ()
  335. {
  336. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  337. cp.Draw (); // Draw is needed to update TrianglePosition
  338. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  339. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  340. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  341. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  342. Assert.Equal ("R:", r.Text);
  343. Assert.Equal (2, r.TrianglePosition);
  344. Assert.IsType<RBar> (r);
  345. Assert.Equal ("G:", g.Text);
  346. Assert.Equal (2, g.TrianglePosition);
  347. Assert.IsType<GBar> (g);
  348. Assert.Equal ("B:", b.Text);
  349. Assert.Equal (2, b.TrianglePosition);
  350. Assert.IsType<BBar> (b);
  351. Assert.Equal ("#000000", hex.Text);
  352. Assert.IsAssignableFrom<IColorBar> (cp.Focused);
  353. cp.Draw (); // Draw is needed to update TrianglePosition
  354. cp.App!.Keyboard.RaiseKeyDownEvent (Key.CursorRight);
  355. cp.Draw (); // Draw is needed to update TrianglePosition
  356. Assert.Equal (3, r.TrianglePosition);
  357. Assert.Equal ("#0F0000", hex.Text);
  358. cp.App!.Keyboard.RaiseKeyDownEvent (Key.CursorRight);
  359. cp.Draw (); // Draw is needed to update TrianglePosition
  360. Assert.Equal (4, r.TrianglePosition);
  361. Assert.Equal ("#1E0000", hex.Text);
  362. // Use cursor to move the triangle all the way to the right
  363. for (var i = 0; i < 1000; i++)
  364. {
  365. cp.App!.Keyboard.RaiseKeyDownEvent (Key.CursorRight);
  366. }
  367. cp.Draw (); // Draw is needed to update TrianglePosition
  368. // 20 width and TrianglePosition is 0 indexed
  369. // Meaning we are asserting that triangle is at end
  370. Assert.Equal (19, r.TrianglePosition);
  371. Assert.Equal ("#FF0000", hex.Text);
  372. cp.App?.Dispose ();
  373. }
  374. [Fact]
  375. public void RGB_MouseNavigation ()
  376. {
  377. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  378. cp.Draw (); // Draw is needed to update TrianglePosition
  379. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  380. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  381. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  382. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  383. Assert.Equal ("R:", r.Text);
  384. Assert.Equal (2, r.TrianglePosition);
  385. Assert.IsType<RBar> (r);
  386. Assert.Equal ("G:", g.Text);
  387. Assert.Equal (2, g.TrianglePosition);
  388. Assert.IsType<GBar> (g);
  389. Assert.Equal ("B:", b.Text);
  390. Assert.Equal (2, b.TrianglePosition);
  391. Assert.IsType<BBar> (b);
  392. Assert.Equal ("#000000", hex.Text);
  393. Assert.IsAssignableFrom<IColorBar> (cp.Focused);
  394. cp.Focused!.RaiseMouseEvent (
  395. new ()
  396. {
  397. Flags = MouseFlags.Button1Pressed,
  398. Position = new (3, 0)
  399. });
  400. cp.Draw (); // Draw is needed to update TrianglePosition
  401. Assert.Equal (3, r.TrianglePosition);
  402. Assert.Equal ("#0F0000", hex.Text);
  403. cp.Focused.RaiseMouseEvent (
  404. new ()
  405. {
  406. Flags = MouseFlags.Button1Pressed,
  407. Position = new (4, 0)
  408. });
  409. cp.Draw (); // Draw is needed to update TrianglePosition
  410. Assert.Equal (4, r.TrianglePosition);
  411. Assert.Equal ("#1E0000", hex.Text);
  412. cp.App?.Dispose ();
  413. }
  414. [Theory]
  415. [MemberData (nameof (ColorPickerTestData))]
  416. public void RGB_NoText (
  417. Color c,
  418. string expectedR,
  419. int expectedRTriangle,
  420. string expectedG,
  421. int expectedGTriangle,
  422. string expectedB,
  423. int expectedBTriangle,
  424. string expectedHex
  425. )
  426. {
  427. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  428. cp.SelectedColor = c;
  429. cp.Draw (); // Draw is needed to update TrianglePosition
  430. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  431. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  432. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  433. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  434. Assert.Equal (expectedR, r.Text);
  435. Assert.Equal (expectedRTriangle, r.TrianglePosition);
  436. Assert.Equal (expectedG, g.Text);
  437. Assert.Equal (expectedGTriangle, g.TrianglePosition);
  438. Assert.Equal (expectedB, b.Text);
  439. Assert.Equal (expectedBTriangle, b.TrianglePosition);
  440. Assert.Equal (expectedHex, hex.Text);
  441. cp.App?.Dispose ();
  442. }
  443. [Theory]
  444. [MemberData (nameof (ColorPickerTestData_WithTextFields))]
  445. public void RGB_NoText_WithTextFields (
  446. Color c,
  447. string expectedR,
  448. int expectedRTriangle,
  449. int expectedRValue,
  450. string expectedG,
  451. int expectedGTriangle,
  452. int expectedGValue,
  453. string expectedB,
  454. int expectedBTriangle,
  455. int expectedBValue,
  456. string expectedHex
  457. )
  458. {
  459. ColorPicker cp = GetColorPicker (ColorModel.RGB, true);
  460. cp.SelectedColor = c;
  461. cp.Draw (); // Draw is needed to update TrianglePosition
  462. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  463. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  464. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  465. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  466. TextField rTextField = GetTextField (cp, ColorPickerPart.Bar1);
  467. TextField gTextField = GetTextField (cp, ColorPickerPart.Bar2);
  468. TextField bTextField = GetTextField (cp, ColorPickerPart.Bar3);
  469. Assert.Equal (expectedR, r.Text);
  470. Assert.Equal (expectedRTriangle, r.TrianglePosition);
  471. Assert.Equal (expectedRValue.ToString (), rTextField.Text);
  472. Assert.Equal (expectedG, g.Text);
  473. Assert.Equal (expectedGTriangle, g.TrianglePosition);
  474. Assert.Equal (expectedGValue.ToString (), gTextField.Text);
  475. Assert.Equal (expectedB, b.Text);
  476. Assert.Equal (expectedBTriangle, b.TrianglePosition);
  477. Assert.Equal (expectedBValue.ToString (), bTextField.Text);
  478. Assert.Equal (expectedHex, hex.Text);
  479. cp.App?.Dispose ();
  480. }
  481. [Fact]
  482. public void SwitchingColorModels_ResetsBars ()
  483. {
  484. ColorPicker cp = GetColorPicker (ColorModel.RGB, false);
  485. cp.SelectedColor = new (255, 0);
  486. cp.Draw (); // Draw is needed to update TrianglePosition
  487. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  488. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  489. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  490. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  491. Assert.Equal ("R:", r.Text);
  492. Assert.Equal (19, r.TrianglePosition);
  493. Assert.Equal ("G:", g.Text);
  494. Assert.Equal (2, g.TrianglePosition);
  495. Assert.Equal ("B:", b.Text);
  496. Assert.Equal (2, b.TrianglePosition);
  497. Assert.Equal ("#FF0000", hex.Text);
  498. // Switch to HSV
  499. cp.Style.ColorModel = ColorModel.HSV;
  500. cp.ApplyStyleChanges ();
  501. cp.Draw (); // Draw is needed to update TrianglePosition
  502. ColorBar h = GetColorBar (cp, ColorPickerPart.Bar1);
  503. ColorBar s = GetColorBar (cp, ColorPickerPart.Bar2);
  504. ColorBar v = GetColorBar (cp, ColorPickerPart.Bar3);
  505. Assert.Equal ("H:", h.Text);
  506. Assert.Equal (2, h.TrianglePosition);
  507. Assert.Equal ("S:", s.Text);
  508. Assert.Equal (19, s.TrianglePosition);
  509. Assert.Equal ("V:", v.Text);
  510. Assert.Equal (19, v.TrianglePosition);
  511. Assert.Equal ("#FF0000", hex.Text);
  512. cp.App?.Dispose ();
  513. }
  514. [Fact]
  515. public void SyncBetweenTextFieldAndBars ()
  516. {
  517. ColorPicker cp = GetColorPicker (ColorModel.RGB, true);
  518. cp.Draw (); // Draw is needed to update TrianglePosition
  519. // Change value using the bar
  520. RBar rBar = cp.SubViews.OfType<RBar> ().First ();
  521. rBar.Value = 128;
  522. cp.Draw (); // Draw is needed to update TrianglePosition
  523. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  524. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  525. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  526. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  527. TextField rTextField = GetTextField (cp, ColorPickerPart.Bar1);
  528. TextField gTextField = GetTextField (cp, ColorPickerPart.Bar2);
  529. TextField bTextField = GetTextField (cp, ColorPickerPart.Bar3);
  530. Assert.Equal ("R:", r.Text);
  531. Assert.Equal (9, r.TrianglePosition);
  532. Assert.Equal ("128", rTextField.Text);
  533. Assert.Equal ("G:", g.Text);
  534. Assert.Equal (2, g.TrianglePosition);
  535. Assert.Equal ("0", gTextField.Text);
  536. Assert.Equal ("B:", b.Text);
  537. Assert.Equal (2, b.TrianglePosition);
  538. Assert.Equal ("0", bTextField.Text);
  539. Assert.Equal ("#800000", hex.Text);
  540. cp.App?.Dispose ();
  541. }
  542. [Fact]
  543. public void TabCompleteColorName ()
  544. {
  545. ColorPicker cp = GetColorPicker (ColorModel.RGB, true, true);
  546. cp.Draw (); // Draw is needed to update TrianglePosition
  547. ColorBar r = GetColorBar (cp, ColorPickerPart.Bar1);
  548. ColorBar g = GetColorBar (cp, ColorPickerPart.Bar2);
  549. ColorBar b = GetColorBar (cp, ColorPickerPart.Bar3);
  550. TextField name = GetTextField (cp, ColorPickerPart.ColorName);
  551. TextField hex = GetTextField (cp, ColorPickerPart.Hex);
  552. name.SetFocus ();
  553. Assert.True (name.HasFocus);
  554. Assert.Same (name, cp.Focused);
  555. name.Text = "";
  556. Assert.Empty (name.Text);
  557. cp.App!.Keyboard.RaiseKeyDownEvent (Key.A);
  558. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Q);
  559. Assert.Equal ("aq", name.Text);
  560. // Auto complete the color name
  561. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Tab);
  562. // Match cyan alternative name
  563. Assert.Equal ("Aqua", name.Text);
  564. Assert.True (name.HasFocus);
  565. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Tab);
  566. // Resolves to cyan color
  567. Assert.Equal ("Aqua", name.Text);
  568. // Tab out of the text field
  569. cp.App!.Keyboard.RaiseKeyDownEvent (Key.Tab);
  570. Assert.False (name.HasFocus);
  571. Assert.NotSame (name, cp.Focused);
  572. Assert.Equal ("#00FFFF", hex.Text);
  573. cp.App?.Dispose ();
  574. }
  575. public static IEnumerable<object []> ColorPickerTestData ()
  576. {
  577. yield return
  578. [
  579. new Color (255, 0),
  580. "R:", 19, "G:", 2, "B:", 2, "#FF0000"
  581. ];
  582. yield return
  583. [
  584. new Color (0, 255),
  585. "R:", 2, "G:", 19, "B:", 2, "#00FF00"
  586. ];
  587. yield return
  588. [
  589. new Color (0, 0, 255),
  590. "R:", 2, "G:", 2, "B:", 19, "#0000FF"
  591. ];
  592. yield return
  593. [
  594. new Color (125, 125, 125),
  595. "R:", 11, "G:", 11, "B:", 11, "#7D7D7D"
  596. ];
  597. }
  598. public static IEnumerable<object []> ColorPickerTestData_WithTextFields ()
  599. {
  600. yield return
  601. [
  602. new Color (255, 0),
  603. "R:", 15, 255, "G:", 2, 0, "B:", 2, 0, "#FF0000"
  604. ];
  605. yield return
  606. [
  607. new Color (0, 255),
  608. "R:", 2, 0, "G:", 15, 255, "B:", 2, 0, "#00FF00"
  609. ];
  610. yield return
  611. [
  612. new Color (0, 0, 255),
  613. "R:", 2, 0, "G:", 2, 0, "B:", 15, 255, "#0000FF"
  614. ];
  615. yield return
  616. [
  617. new Color (125, 125, 125),
  618. "R:", 9, 125, "G:", 9, 125, "B:", 9, 125, "#7D7D7D"
  619. ];
  620. }
  621. private ColorBar GetColorBar (ColorPicker cp, ColorPickerPart toGet)
  622. {
  623. if (toGet <= ColorPickerPart.Bar3)
  624. {
  625. return cp.SubViews.OfType<ColorBar> ().ElementAt ((int)toGet);
  626. }
  627. throw new NotSupportedException ("ColorPickerPart must be a bar");
  628. }
  629. private static ColorPicker GetColorPicker (ColorModel colorModel, bool showTextFields, bool showName = false)
  630. {
  631. IApplication? app = Application.Create ();
  632. app.Init ("Fake");
  633. var cp = new ColorPicker { Width = 20, SelectedColor = new (0, 0) };
  634. cp.Style.ColorModel = colorModel;
  635. cp.Style.ShowTextFields = showTextFields;
  636. cp.Style.ShowColorName = showName;
  637. cp.ApplyStyleChanges ();
  638. Runnable<bool>? runnable = new () { Width = 20, Height = 5 };
  639. app.Begin (runnable);
  640. runnable.Add (cp);
  641. app.LayoutAndDraw ();
  642. return cp;
  643. }
  644. private TextField GetTextField (ColorPicker cp, ColorPickerPart toGet)
  645. {
  646. bool hasBarValueTextFields = cp.Style.ShowTextFields;
  647. bool hasColorNameTextField = cp.Style.ShowColorName;
  648. switch (toGet)
  649. {
  650. case ColorPickerPart.Bar1:
  651. case ColorPickerPart.Bar2:
  652. case ColorPickerPart.Bar3:
  653. if (!hasBarValueTextFields)
  654. {
  655. throw new NotSupportedException ("Corresponding Style option is not enabled");
  656. }
  657. return cp.SubViews.OfType<TextField> ().ElementAt ((int)toGet);
  658. case ColorPickerPart.ColorName:
  659. if (!hasColorNameTextField)
  660. {
  661. throw new NotSupportedException ("Corresponding Style option is not enabled");
  662. }
  663. return cp.SubViews.OfType<TextField> ().ElementAt (hasBarValueTextFields ? (int)toGet : (int)toGet - 3);
  664. case ColorPickerPart.Hex:
  665. int offset = hasBarValueTextFields ? 0 : 3;
  666. offset += hasColorNameTextField ? 0 : 1;
  667. return cp.SubViews.OfType<TextField> ().ElementAt ((int)toGet - offset);
  668. default:
  669. throw new ArgumentOutOfRangeException (nameof (toGet), toGet, null);
  670. }
  671. }
  672. private enum ColorPickerPart
  673. {
  674. Bar1 = 0,
  675. Bar2 = 1,
  676. Bar3 = 2,
  677. ColorName = 3,
  678. Hex = 4
  679. }
  680. }