/cui/window.d

http://github.com/wilkie/djehuty · D · 761 lines · 569 code · 135 blank · 57 comment · 138 complexity · f1516aa135c2e74fe817d33670045d6e MD5 · raw file

  1. module cui.window;
  2. import cui.application;
  3. import cui.widget;
  4. import cui.container;
  5. import cui.dialog;
  6. import djehuty;
  7. import resource.menu;
  8. import io.console;
  9. // Description: This class abstacts the console window and allows for high level console operations which are abstracted away as controls. It is the Window class for the console world.
  10. class CuiWindow : Responder {
  11. // Constructor
  12. this() {
  13. this("CuiWindow");
  14. }
  15. this(Color bgClr) {
  16. this(bgClr, "CuiWindow");
  17. }
  18. this(Color bgClr, string name) {
  19. Console.clipRect(0,0,this.width, this.height);
  20. _bgClr = bgClr;
  21. _controlContainer = new CuiContainer(0, 0, this.width, this.height);
  22. _controlContainer.text = name.dup;
  23. _controlContainer._window = this;
  24. push(_controlContainer);
  25. }
  26. this(string name) {
  27. Console.clipRect(0,0,this.width, this.height);
  28. _controlContainer = new CuiContainer(0, 0, this.width, this.height);
  29. _controlContainer.text = name.dup;
  30. _controlContainer._window = this;
  31. push(_controlContainer);
  32. }
  33. // Events
  34. void onInitialize() {
  35. // go through control list, init
  36. Console.backcolor = _bgClr;
  37. Console.clear();
  38. Console.clipRect(0,0,this.width, this.height);
  39. _controlContainer.onInit();
  40. _inited = true;
  41. redraw();
  42. _controlContainer.onGotFocus();
  43. }
  44. void onUninitialize() {
  45. }
  46. void onResize() {
  47. }
  48. void redraw() {
  49. if (_inited == false) { return; }
  50. Console.hideCaret();
  51. Console.clipClear();
  52. _controlContainer.onDraw();
  53. _drawMenu();
  54. Console.clipClear();
  55. }
  56. void onKeyDown(Key key) {
  57. if (_focusedMenu !is null) {
  58. if (_focusedMenu == _selectedMenu) {
  59. // on the menu bar
  60. if (key.code == Key.Down) {
  61. // expand menu
  62. if (_focusedMenu.length > 0) {
  63. _selectMenu(_focusedMenu);
  64. }
  65. }
  66. else if (key.code == Key.Up) {
  67. // leave menu
  68. _focusedMenu = null;
  69. _selectedMenu = null;
  70. _drawMenu();
  71. // Focus on the current widget
  72. _controlContainer.onGotFocus();
  73. }
  74. else if (key.code == Key.Left) {
  75. if (_focusedMenuIndex != 0) {
  76. _focusedMenuIndex--;
  77. _selectedMenu = _menu[_focusedMenuIndex];
  78. _focusedMenu = _selectedMenu;
  79. _drawMenu();
  80. }
  81. else {
  82. // leave menu
  83. _focusedMenu = null;
  84. _selectedMenu = null;
  85. _drawMenu();
  86. // Focus on the current widget
  87. _controlContainer.onGotFocus();
  88. }
  89. }
  90. else if (key.code == Key.Right) {
  91. if ((_focusedMenuIndex + 1) < _menu.length) {
  92. _focusedMenuIndex++;
  93. _selectedMenu = _menu[_focusedMenuIndex];
  94. _focusedMenu = _selectedMenu;
  95. _drawMenu();
  96. }
  97. }
  98. else if (key.code == Key.Return || key.code == Key.Space) {
  99. // apply
  100. if (_selectedMenu.length > 0) {
  101. // it is a submenu
  102. _selectMenu(_selectedMenu);
  103. }
  104. else {
  105. // select the item
  106. _cancelNextChar = true;
  107. _focusedMenu = null;
  108. Menu selected = _selectedMenu;
  109. _selectedMenu = null;
  110. _drawMenu();
  111. onMenu(selected);
  112. // Focus on the current widget
  113. _controlContainer.onGotFocus();
  114. }
  115. }
  116. else if (key.code >= Key.A || key.code <= Key.Z) {
  117. // check for hint
  118. char keychr = cast(char)(key.code - Key.A) + 'a';
  119. foreach(mnu; _menu) {
  120. if (mnu.hint == keychr) {
  121. // Select this menu
  122. _selectedMenu = mnu;
  123. if (_selectedMenu.length > 0) {
  124. // it is a submenu
  125. _selectMenu(_selectedMenu);
  126. }
  127. else {
  128. // select the item
  129. _cancelNextChar = true;
  130. _focusedMenu = null;
  131. Menu selected = _selectedMenu;
  132. _selectedMenu = null;
  133. _drawMenu();
  134. onMenu(selected);
  135. // Focus on the current widget
  136. _controlContainer.onGotFocus();
  137. }
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. else {
  144. // within a submenu
  145. if (key.code == Key.Down) {
  146. size_t tmpIndex = _selectedMenuIndex;
  147. tmpIndex++;
  148. while (tmpIndex < _focusedMenu.length) {
  149. if (_focusedMenu[tmpIndex].displayText().trim() != "") {
  150. _selectedMenuIndex = tmpIndex;
  151. _selectedMenu = _focusedMenu[_selectedMenuIndex];
  152. _drawSubmenu();
  153. return;
  154. }
  155. tmpIndex++;
  156. }
  157. }
  158. else if (key.code == Key.Up) {
  159. size_t tmpIndex = _selectedMenuIndex;
  160. while (tmpIndex > 0) {
  161. tmpIndex--;
  162. if (_focusedMenu[tmpIndex].displayText().trim() != "") {
  163. _selectedMenuIndex = tmpIndex;
  164. _selectedMenu = _focusedMenu[_selectedMenuIndex];
  165. _drawSubmenu();
  166. return;
  167. }
  168. }
  169. if (_selectedMenuIndex > 0) {
  170. _selectedMenuIndex--;
  171. _selectedMenu = _focusedMenu[_selectedMenuIndex];
  172. _drawSubmenu();
  173. }
  174. }
  175. else if (key.code == Key.Left) {
  176. // remove submenu
  177. if (_selectedMenu is null) {
  178. }
  179. else {
  180. _removeMenuContext();
  181. }
  182. }
  183. else if (key.code == Key.Right) {
  184. // add submenu
  185. if (_selectedMenu.length > 0) {
  186. _addSubmenu();
  187. _drawSubmenu();
  188. }
  189. }
  190. else if (key.code == Key.Return || key.code == Key.Space) {
  191. // apply
  192. if (_selectedMenu.length > 0) {
  193. // it is a submenu
  194. _addSubmenu();
  195. _drawSubmenu();
  196. }
  197. else {
  198. // select the item
  199. _cancelNextChar = true;
  200. _focusedMenu = null;
  201. Menu selected = _selectedMenu;
  202. _selectedMenu = null;
  203. redraw();
  204. onMenu(selected);
  205. // Focus on the current widget
  206. _controlContainer.onGotFocus();
  207. }
  208. }
  209. else if (key.code >= Key.A && key.code <= Key.Z) {
  210. char chr = cast(char)(key.code - Key.A) + 'a';
  211. // Look for a hint
  212. foreach(uint i, mnuItem; _focusedMenu) {
  213. // Does the menu item have a hint?
  214. if (mnuItem.hintPosition == -1) {
  215. continue;
  216. }
  217. char hint = mnuItem.hint;
  218. if (chr == hint) {
  219. // This menu item is selected
  220. _selectedMenuIndex = i;
  221. _selectedMenu = mnuItem;
  222. _drawSubmenu();
  223. if (_selectedMenu.length > 0) {
  224. // it is a submenu
  225. _addSubmenu();
  226. _drawSubmenu();
  227. }
  228. else {
  229. // select the item
  230. _cancelNextChar = true;
  231. _focusedMenu = null;
  232. Menu selected = _selectedMenu;
  233. _selectedMenu = null;
  234. redraw();
  235. onMenu(selected);
  236. // Focus on the current widget
  237. _controlContainer.onGotFocus();
  238. }
  239. }
  240. }
  241. }
  242. }
  243. // Do not pass the key off to a widget
  244. return;
  245. }
  246. else if (_menu !is null) {
  247. if (key.alt) {
  248. uint chr = key.code - Key.A;
  249. if (chr <= Key.Z) {
  250. // compare to menu hints
  251. foreach(mnuItem; _menu) {
  252. // Does the menu item have a hint?
  253. if (mnuItem.hintPosition == -1) {
  254. continue;
  255. }
  256. auto hint = mnuItem.displayText().lowercase()[mnuItem.hintPosition];
  257. uint chr2 = cast(uint)(hint - 'a');
  258. if (chr == chr2) {
  259. // This menu item is selected
  260. _selectMenu(mnuItem);
  261. // Do not pass the key off to a widget
  262. return;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. _controlContainer.onKeyDown(key);
  269. }
  270. void onMenu(Menu mnu) {
  271. }
  272. void onKeyChar(dchar keyChar) {
  273. if (_focusedMenu !is null || _cancelNextChar) {
  274. _cancelNextChar = false;
  275. return;
  276. }
  277. _controlContainer.onKeyChar(keyChar);
  278. }
  279. void onPrimaryMouseDown() {
  280. _controlContainer.onPrimaryMouseDown();
  281. }
  282. void onPrimaryMouseUp() {
  283. _controlContainer.onPrimaryMouseUp();
  284. }
  285. void onSecondaryMouseDown() {
  286. _controlContainer.onSecondaryMouseDown();
  287. }
  288. void onSecondaryMouseUp() {
  289. _controlContainer.onSecondaryMouseUp();
  290. }
  291. void onTertiaryMouseDown() {
  292. _controlContainer.onTertiaryMouseDown();
  293. }
  294. void onTertiaryMouseUp() {
  295. _controlContainer.onTertiaryMouseUp();
  296. }
  297. void onOtherMouseDown(uint button) {
  298. // if (_focused_control !is null) {
  299. // }
  300. }
  301. void onOtherMouseUp(uint button) {
  302. // if (_focused_control !is null) {
  303. // }
  304. }
  305. void onMouseWheelY(uint amount) {
  306. _controlContainer.onMouseWheelY(amount);
  307. }
  308. void onMouseWheelX(uint amount) {
  309. _controlContainer.onMouseWheelX(amount);
  310. }
  311. void onMouseMove() {
  312. _controlContainer.onMouseMove();
  313. }
  314. void text(string value) {
  315. _controlContainer.text(value);
  316. }
  317. string text() {
  318. return _controlContainer.text;
  319. }
  320. uint width() {
  321. return Console.width();
  322. }
  323. uint height() {
  324. return Console.height();
  325. }
  326. // Methods
  327. override void push(Dispatcher dsp) {
  328. if (dsp is _controlContainer) {
  329. super.push(dsp);
  330. }
  331. else if (cast(CuiWidget)dsp) {
  332. _controlContainer.push(cast(CuiWidget)dsp);
  333. }
  334. else {
  335. super.push(dsp);
  336. }
  337. }
  338. Color backcolor() {
  339. return _bgClr;
  340. }
  341. CuiApplication application() {
  342. return cast(CuiApplication)this.responder;
  343. }
  344. bool isActive() {
  345. return (application() !is null && application.window is this);
  346. }
  347. void menu(Menu mnu) {
  348. _menu = mnu;
  349. if (isActive) {
  350. _drawMenu();
  351. }
  352. // Turn off all rendering
  353. Console.clipRect(0,0,this.width,this.height);
  354. // Resize control container to take into account menubar
  355. _controlContainer.resize(this.width, this.height-1);
  356. // Move control container below the menu bar
  357. _controlContainer.move(0,1);
  358. // clear clipping region
  359. Console.clipClear();
  360. redraw();
  361. }
  362. Mouse mouseProps;
  363. private:
  364. package final void _onResize() {
  365. if (_menu) {
  366. _controlContainer.resize(this.width, this.height - 1);
  367. }
  368. else {
  369. _controlContainer.resize(this.width, this.height);
  370. }
  371. onResize();
  372. }
  373. void _drawSubmenu() {
  374. MenuContext context = _menus[$-1];
  375. Menu mnu = context.submenu;
  376. uint x = context.region.left;
  377. uint y = context.region.top;
  378. uint maxLength = context.region.right - context.region.left;
  379. foreach(subItem; mnu) {
  380. Console.position(x, y);
  381. if (subItem is _selectedMenu) {
  382. Console.forecolor = Color.White;
  383. Console.backcolor = Color.Blue;
  384. }
  385. else {
  386. Console.forecolor = Color.Black;
  387. Console.backcolor = Color.White;
  388. }
  389. Console.put(" ");
  390. int padding = maxLength - subItem.displayText.length;
  391. _drawMenuItem(subItem, false, maxLength - subItem.displayText.length);
  392. if (subItem is _selectedMenu) {
  393. Console.forecolor = Color.White;
  394. Console.backcolor = Color.Blue;
  395. }
  396. else {
  397. Console.forecolor = Color.Black;
  398. Console.backcolor = Color.White;
  399. }
  400. Console.put(" ");
  401. y++;
  402. }
  403. }
  404. void _drawMenuItem(Menu mnuItem, bool drawHints = false, uint padding = 0) {
  405. if (mnuItem is _selectedMenu) {
  406. Console.forecolor = Color.Gray;
  407. Console.backcolor = Color.Blue;
  408. }
  409. else {
  410. Console.forecolor = Color.Black;
  411. Console.backcolor = Color.Gray;
  412. }
  413. if (mnuItem.hintPosition >= 0) {
  414. if (mnuItem.hintPosition > 0) {
  415. Console.put(mnuItem.displayText[0..mnuItem.hintPosition]);
  416. }
  417. if (mnuItem !is _selectedMenu) {
  418. if (drawHints) {
  419. Console.forecolor = Color.Blue;
  420. }
  421. else {
  422. Console.forecolor = Color.DarkBlue;
  423. }
  424. }
  425. Console.put(mnuItem.displayText[mnuItem.hintPosition]);
  426. if (mnuItem !is _selectedMenu) {
  427. Console.forecolor = Color.Black;
  428. Console.backcolor = Color.White;
  429. }
  430. if (mnuItem.hintPosition < mnuItem.displayText.length) {
  431. Console.put(mnuItem.displayText[mnuItem.hintPosition + 1..mnuItem.displayText.length]);
  432. }
  433. }
  434. else {
  435. Console.put(mnuItem.displayText);
  436. }
  437. // Padding
  438. for (uint i; i < padding; i++) {
  439. Console.put(" ");
  440. }
  441. if (mnuItem is _selectedMenu) {
  442. Console.forecolor = Color.Black;
  443. Console.backcolor = Color.White;
  444. }
  445. }
  446. void _drawMenu(bool drawHints = false) {
  447. if (_menu is null) {
  448. return;
  449. }
  450. uint curWidth = this.width;
  451. Console.position(0,0);
  452. Console.forecolor = Color.Black;
  453. Console.backcolor = Color.White;
  454. if (_menu.length > 0 && (_menu[0] is _selectedMenu)) {
  455. Console.forecolor = Color.White;
  456. Console.backcolor = Color.Blue;
  457. }
  458. else {
  459. Console.forecolor = Color.White;
  460. Console.backcolor = Color.Blue;
  461. }
  462. Console.put(" ");
  463. curWidth--;
  464. foreach(i, mnuItem; _menu) {
  465. if (curWidth > mnuItem.displayText.length) {
  466. _drawMenuItem(mnuItem, drawHints, 0);
  467. if (mnuItem is _selectedMenu || (((i + 1) < _menu.length) && _menu[i+1] is _selectedMenu)) {
  468. Console.forecolor = Color.White;
  469. Console.backcolor = Color.Blue;
  470. Console.put(" ");
  471. Console.forecolor = Color.Black;
  472. Console.backcolor = Color.White;
  473. }
  474. else {
  475. Console.put(" ");
  476. }
  477. curWidth -= (mnuItem.displayText.length + 1);
  478. }
  479. }
  480. bool drawCaption = false;
  481. if (this.text !is null && curWidth >= (this.text.length + 1)) {
  482. curWidth -= this.text.length + 1;
  483. drawCaption = true;
  484. }
  485. if (curWidth > 0) {
  486. for (; curWidth != 0; curWidth--) {
  487. Console.put(" ");
  488. }
  489. }
  490. if (drawCaption) {
  491. Console.put(this.text, " ");
  492. }
  493. }
  494. void _selectMenu(Menu mnu) {
  495. // Switching focus to window
  496. _controlContainer.onLostFocus();
  497. // Do not show the cursor within a menu
  498. Console.hideCaret();
  499. // draw menu
  500. if (_menu is null) {
  501. return;
  502. }
  503. if (mnu.isChildOf(_menu)) {
  504. // Find this menu is the root menu
  505. _selectedMenu = mnu;
  506. _selectedMenuIndex = 0;
  507. _drawMenu();
  508. if (mnu.length == 0) {
  509. onMenu(mnu);
  510. _selectedMenu = null;
  511. _selectedMenuIndex = 0;
  512. _drawMenu();
  513. return;
  514. }
  515. else {
  516. uint curPos = 1;
  517. uint focusedIdx;
  518. foreach(uint idx, mnuItem; _menu) {
  519. if (mnuItem is mnu) {
  520. focusedIdx = idx;
  521. break;
  522. }
  523. curPos += mnuItem.displayText.length;
  524. curPos ++;
  525. }
  526. _focusedMenuIndex = focusedIdx;
  527. _focusedMenu = mnu;
  528. _addSubmenuContext(curPos, 1, mnu);
  529. if (mnu.length > 0) {
  530. _selectedMenu = mnu[0];
  531. _selectedMenuIndex = 0;
  532. }
  533. else {
  534. _selectedMenu = null;
  535. }
  536. _drawSubmenu();
  537. }
  538. }
  539. }
  540. void _addSubmenu() {
  541. uint x;
  542. uint y;
  543. if (_menus.length == 0) {
  544. return;
  545. }
  546. // This menu starts at the right of the current one + 2 (for the padding to either side)
  547. x = _menus[$-1].region.right + 2;
  548. y = _menus[$-1].region.top + _selectedMenuIndex;
  549. _addSubmenuContext(x, y, _selectedMenu);
  550. _focusedMenuIndex = _selectedMenuIndex;
  551. _focusedMenu = _selectedMenu;
  552. if (_focusedMenu.length > 0) {
  553. _selectedMenu = _focusedMenu[0];
  554. _selectedMenuIndex = 0;
  555. }
  556. else {
  557. _selectedMenu = null;
  558. }
  559. }
  560. void _addSubmenuContext(uint x, uint y, Menu mnu) {
  561. // get width of submenu
  562. uint maxLength;
  563. foreach(subItem; mnu) {
  564. if (subItem.displayText.length > maxLength) {
  565. maxLength = subItem.displayText.length;
  566. }
  567. }
  568. MenuContext mnuContext;
  569. mnuContext.submenu = mnu;
  570. mnuContext.region.left = x;
  571. mnuContext.region.right = x + maxLength;
  572. mnuContext.region.top = y;
  573. mnuContext.region.bottom = y + mnu.length;
  574. mnuContext.oldSelectedMenu = _selectedMenu;
  575. mnuContext.oldSelectedIndex = _selectedMenuIndex;
  576. mnuContext.oldFocusedMenu = _focusedMenu;
  577. mnuContext.oldFocusedIndex = _focusedMenuIndex;
  578. _menus ~= mnuContext;
  579. }
  580. void _removeMenuContext() {
  581. if (_menus.length == 0) {
  582. return;
  583. }
  584. MenuContext removed = _menus[$-1];
  585. removed.region.right += 2;
  586. _menus = _menus[0..$-1];
  587. _selectedMenu = removed.oldSelectedMenu;
  588. _selectedMenuIndex = removed.oldSelectedIndex;
  589. _focusedMenu = removed.oldFocusedMenu;
  590. _focusedMenuIndex = removed.oldFocusedIndex;
  591. Console.clipClear();
  592. Console.clipRect(0, 0, removed.region.left, this.height);
  593. Console.clipRect(removed.region.left, 0, removed.region.right, removed.region.top);
  594. Console.clipRect(removed.region.left, removed.region.bottom, removed.region.right, this.height);
  595. Console.clipRect(removed.region.right, 0, this.width, this.height);
  596. _controlContainer.onDraw();
  597. Console.clipClear();
  598. if (_menus.length > 0) {
  599. // _drawSubmenu();
  600. }
  601. }
  602. Color _bgClr = Color.Black;
  603. // head and tail of the control linked list
  604. CuiWidget _firstControl; //head
  605. CuiWidget _lastControl; //tail
  606. int _numControls = 0;
  607. // Current Menu
  608. Menu _menu;
  609. // In a menu?
  610. Menu _focusedMenu;
  611. uint _focusedMenuIndex;
  612. struct MenuContext {
  613. Menu submenu;
  614. Menu oldSelectedMenu;
  615. uint oldSelectedIndex;
  616. Menu oldFocusedMenu;
  617. uint oldFocusedIndex;
  618. Rect region;
  619. }
  620. Menu _selectedMenu;
  621. uint _selectedMenuIndex;
  622. CuiContainer _controlContainer;
  623. MenuContext[] _menus;
  624. bool _cancelNextChar;
  625. bool _inited;
  626. }