Dropdown.hx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package h2d;
  2. private class Fake extends Object {
  3. var dd : Dropdown;
  4. public function new(dd : Dropdown) {
  5. super(dd);
  6. this.dd = dd;
  7. }
  8. override function getBoundsRec(relativeTo:Object, out:h2d.col.Bounds, forSize:Bool) {
  9. super.getBoundsRec(relativeTo, out, forSize);
  10. if (dd.selectedItem >= 0) {
  11. var item = @:privateAccess dd.getItem(dd.selectedItem);
  12. var size = item.getSize();
  13. addBounds(relativeTo, out, 0, 0, size.width, size.height);
  14. }
  15. }
  16. override function draw(ctx) {
  17. if (dd.selectedItem >= 0) {
  18. var item = @:privateAccess dd.getItem(dd.selectedItem);
  19. var oldX = item.absX;
  20. var oldY = item.absY;
  21. item.absX = absX;
  22. item.absY = absY;
  23. for( c in item ) c.posChanged = true;
  24. item.drawRec(ctx);
  25. for( c in item ) c.posChanged = true;
  26. item.absX = oldX;
  27. item.absY = oldY;
  28. }
  29. }
  30. }
  31. /**
  32. A simple UI component that creates an interactive drop-down list.
  33. Dropdown will add an `h2d.Flow` to the `Scene` when opening in order to be visible above other objects. See `Dropdown.dropdownLayer` for more details.
  34. There is no handling of user input on items, and implementation of selection and other actions is up to the user.
  35. Note that when `dropdownList` opens and closes, item objects will receive the `onHierarchyChanged` callback.
  36. **/
  37. @:uiNoComponent
  38. class Dropdown extends Flow {
  39. var fake : Fake;
  40. var cursor : h2d.Bitmap;
  41. var arrow : h2d.Bitmap;
  42. /**
  43. A background Tile that is shown when user hover over an item in the dropdown list.
  44. The tile will be stretched to cover full row width and item height during rendering.
  45. **/
  46. public var tileOverItem(default, set) : h2d.Tile;
  47. /**
  48. A Tile used to visualize an arrow of the dropdown when the list is closed.
  49. **/
  50. public var tileArrow(default, set) : h2d.Tile;
  51. /**
  52. A Tile used to visualize and arrow of the dropdown when the list is open.
  53. **/
  54. public var tileArrowOpen : h2d.Tile;
  55. /**
  56. When disabled, the user would not be able to change the selected item.
  57. **/
  58. public var canEdit(default,set) : Bool = true;
  59. /**
  60. A reference to the Flow that will contain the items.
  61. **/
  62. public var dropdownList : Flow;
  63. /**
  64. A Scene layer to which `dropdownList` will be added when opening dropdown.
  65. **/
  66. public var dropdownLayer : Int = 0;
  67. /**
  68. Currently selected item index. To deselect an item, set it to `-1`.
  69. **/
  70. public var selectedItem(default, set) : Int = -1;
  71. /**
  72. Currently highlighted item index.
  73. **/
  74. public var highlightedItem(default, null) : Int = -1;
  75. /**
  76. When enabled, the dropdown list will appear above the dropdown.
  77. **/
  78. public var rollUp : Bool = false;
  79. /**
  80. Create a new Dropdown with given parent.
  81. @param parent An optional parent `h2d.Object` instance to which Dropdown adds itself if set.
  82. **/
  83. public function new(?parent) {
  84. super(parent);
  85. canEdit = true;
  86. minHeight = maxHeight = 21;
  87. paddingLeft = 5;
  88. verticalAlign = Middle;
  89. reverse = true;
  90. tileOverItem = h2d.Tile.fromColor(0x303030, 1, 1);
  91. tileArrow = tileArrowOpen = h2d.Tile.fromColor(0x404040, maxHeight - 2, maxHeight - 2);
  92. backgroundTile = h2d.Tile.fromColor(0x101010);
  93. borderHeight = borderWidth = 1;
  94. dropdownList = new Flow(this);
  95. dropdownList.layout = Vertical;
  96. dropdownList.borderHeight = dropdownList.borderWidth = 1;
  97. dropdownList.paddingLeft = paddingLeft;
  98. dropdownList.visible = false;
  99. cursor = new h2d.Bitmap(tileOverItem, dropdownList);
  100. dropdownList.getProperties(cursor).isAbsolute = true;
  101. arrow = new h2d.Bitmap(tileArrow, this);
  102. var p = getProperties(arrow);
  103. p.horizontalAlign = Right;
  104. p.verticalAlign = Top;
  105. inline function setItem(i) {
  106. if( selectedItem != i ) {
  107. selectedItem = i;
  108. onChange(getItem(i));
  109. }
  110. }
  111. //
  112. fake = new Fake(this);
  113. enableInteractive = true;
  114. interactive.onPush = function(e:hxd.Event) {
  115. if( e.button == 0 && canEdit )
  116. interactive.focus();
  117. }
  118. interactive.onClick = function(e) {
  119. if (dropdownList.parent != this ) {
  120. close();
  121. } else if( canEdit ) {
  122. var bds = this.getBounds();
  123. dropdownList.x = bds.xMin;
  124. dropdownList.minWidth = this.minWidth;
  125. open();
  126. dropdownList.y = rollUp ? bds.yMin - dropdownList.getSize().height : bds.yMax;
  127. }
  128. }
  129. interactive.onFocusLost = function(e) {
  130. if (highlightedItem >= 0 && canEdit) {
  131. setItem(highlightedItem);
  132. }
  133. close();
  134. }
  135. dropdownList.enableInteractive = true;
  136. dropdownList.interactive.onClick = function(e) {
  137. if( canEdit ) setItem(highlightedItem);
  138. close();
  139. }
  140. dropdownList.interactive.onMove = function(e : hxd.Event) {
  141. var clickPos = dropdownList.localToGlobal(new h2d.col.Point(e.relX, e.relY));
  142. var items = getItems();
  143. for (i in 0...items.length) {
  144. var item = items[i];
  145. var bds = item.getBounds();
  146. if (clickPos.y >= bds.yMin && clickPos.y < bds.yMax) {
  147. if (highlightedItem != i) {
  148. if (highlightedItem >= 0) {
  149. onOutItem(items[highlightedItem]);
  150. }
  151. highlightedItem = i;
  152. if (cursor.tile.width != 0 && cursor.tile.height != 0) {
  153. cursor.visible = true;
  154. cursor.x = 1;
  155. cursor.y = item.y;
  156. cursor.tile.width = minWidth - 2;
  157. cursor.tile.height = Std.int(item.getSize().height);
  158. }
  159. onOverItem(item);
  160. }
  161. break;
  162. }
  163. }
  164. }
  165. dropdownList.interactive.onOut = function(e : hxd.Event) {
  166. onOutItem(getItem(highlightedItem));
  167. highlightedItem = -1;
  168. cursor.visible = false;
  169. }
  170. needReflow = true;
  171. }
  172. function getItems() : Array<h2d.Object> {
  173. return [for( i => obj in dropdownList.children ) if( !dropdownList.properties[i].isAbsolute ) obj];
  174. }
  175. function getItem(index) {
  176. return getItems()[index];
  177. }
  178. override function set_backgroundTile(t) {
  179. super.set_backgroundTile(t);
  180. if(dropdownList != null) dropdownList.backgroundTile = t;
  181. return t;
  182. }
  183. function set_tileArrow(t) {
  184. if(arrow != null) arrow.tile = t;
  185. return tileArrow = t;
  186. }
  187. function set_tileOverItem(t) {
  188. if(cursor != null) cursor.tile = t;
  189. return tileOverItem = t;
  190. }
  191. /**
  192. Adds the Object `s` to the dropdown list. `s` is not restricted to be the same type across all items.
  193. **/
  194. public function addItem(s : Object) {
  195. dropdownList.addChild(s);
  196. var width = Std.int(dropdownList.getSize().width);
  197. if( maxWidth != null && width > maxWidth ) width = maxWidth;
  198. minWidth = hxd.Math.imax(minWidth, Std.int(width-arrow.getSize().width));
  199. }
  200. function set_canEdit(b) {
  201. if( !b ) close();
  202. alpha = b ? 1 : 0.7;
  203. return canEdit = b;
  204. }
  205. function set_selectedItem(s) {
  206. var items = getItems();
  207. if( s < 0 )
  208. s = -1;
  209. else if( s >= items.length )
  210. s = items.length - 1;
  211. var item = items[s];
  212. if( item != null )
  213. minHeight = Std.int(item.getSize().height);
  214. needReflow = true;
  215. return selectedItem = s;
  216. }
  217. /**
  218. Programmatically opens the dropdown, showing the dropdown list.
  219. **/
  220. public function open() {
  221. if( dropdownList.parent == this ) {
  222. getScene().add(dropdownList, dropdownLayer);
  223. dropdownList.visible = true;
  224. arrow.tile = tileArrowOpen;
  225. onOpen();
  226. }
  227. }
  228. /**
  229. Programmatically closes the dropdown, hiding the dropdown list.
  230. **/
  231. public function close() {
  232. if( dropdownList.parent != this ) {
  233. addChild(dropdownList);
  234. dropdownList.visible = false;
  235. arrow.tile = tileArrow;
  236. onClose();
  237. }
  238. }
  239. override function onRemove() {
  240. super.onRemove();
  241. if( dropdownList.parent != this )
  242. dropdownList.remove();
  243. }
  244. /**
  245. Sent when dropdown is being opened. Triggered both by user input and programmatic action via `Dropdown.open`.
  246. **/
  247. public dynamic function onOpen() {
  248. }
  249. /**
  250. Sent when dropdown is being closed. Triggered both by user input and programmatic action via `Dropdown.close`.
  251. **/
  252. public dynamic function onClose() {
  253. }
  254. /**
  255. Sent when user change the item in the list.
  256. @param item An object that was hovered.
  257. **/
  258. public dynamic function onChange(item : Object) {
  259. }
  260. /**
  261. Sent when user hovers over an item in the dropdown list.
  262. @param item An object that was hovered.
  263. **/
  264. public dynamic function onOverItem(item : Object) {
  265. }
  266. /**
  267. Sent when user moves mouse away from an item in the dropdown list.
  268. @param item An item that was hovered previously.
  269. **/
  270. public dynamic function onOutItem(item : Object) {
  271. }
  272. }