grid.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { Container } from "./container";
  2. import { ValueAndUnit } from "../valueAndUnit";
  3. import { Control } from "./control";
  4. import { Measure } from "../measure";
  5. /**
  6. * Class used to create a 2D grid container
  7. */
  8. export class Grid extends Container {
  9. private _rowDefinitions = new Array<ValueAndUnit>();
  10. private _columnDefinitions = new Array<ValueAndUnit>();
  11. private _cells: { [key: string]: Container } = {};
  12. private _childControls = new Array<Control>();
  13. /** Gets the list of children */
  14. public get children(): Control[] {
  15. return this._childControls;
  16. }
  17. /**
  18. * Adds a new row to the grid
  19. * @param height defines the height of the row (either in pixel or a value between 0 and 1)
  20. * @param isPixel defines if the height is expressed in pixel (or in percentage)
  21. * @returns the current grid
  22. */
  23. public addRowDefinition(height: number, isPixel = false): Grid {
  24. this._rowDefinitions.push(new ValueAndUnit(height, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE));
  25. this._markAsDirty();
  26. return this;
  27. }
  28. /**
  29. * Adds a new column to the grid
  30. * @param width defines the width of the column (either in pixel or a value between 0 and 1)
  31. * @param isPixel defines if the width is expressed in pixel (or in percentage)
  32. * @returns the current grid
  33. */
  34. public addColumnDefinition(width: number, isPixel = false): Grid {
  35. this._columnDefinitions.push(new ValueAndUnit(width, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE));
  36. this._markAsDirty();
  37. return this;
  38. }
  39. /**
  40. * Update a row definition
  41. * @param index defines the index of the row to update
  42. * @param height defines the height of the row (either in pixel or a value between 0 and 1)
  43. * @param isPixel defines if the weight is expressed in pixel (or in percentage)
  44. * @returns the current grid
  45. */
  46. public setRowDefinition(index: number, height: number, isPixel = false): Grid {
  47. if (index < 0 || index >= this._rowDefinitions.length) {
  48. return this;
  49. }
  50. this._rowDefinitions[index] = new ValueAndUnit(height, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
  51. this._markAsDirty();
  52. return this;
  53. }
  54. /**
  55. * Update a column definition
  56. * @param index defines the index of the column to update
  57. * @param width defines the width of the column (either in pixel or a value between 0 and 1)
  58. * @param isPixel defines if the width is expressed in pixel (or in percentage)
  59. * @returns the current grid
  60. */
  61. public setColumnDefinition(index: number, width: number, isPixel = false): Grid {
  62. if (index < 0 || index >= this._columnDefinitions.length) {
  63. return this;
  64. }
  65. this._columnDefinitions[index] = new ValueAndUnit(width, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
  66. this._markAsDirty();
  67. return this;
  68. }
  69. private _removeCell(cell: Container, key: string) {
  70. if (!cell) {
  71. return;
  72. }
  73. super.removeControl(cell);
  74. for (var control of cell.children) {
  75. let childIndex = this._childControls.indexOf(control);
  76. if (childIndex !== -1) {
  77. this._childControls.splice(childIndex, 1);
  78. }
  79. }
  80. delete this._cells[key];
  81. }
  82. private _offsetCell(previousKey: string, key: string) {
  83. if (!this._cells[key]) {
  84. return;
  85. }
  86. this._cells[previousKey] = this._cells[key];
  87. for (var control of this._cells[previousKey].children) {
  88. control._tag = previousKey;
  89. }
  90. delete this._cells[key];
  91. }
  92. /**
  93. * Remove a column definition at specified index
  94. * @param index defines the index of the column to remove
  95. * @returns the current grid
  96. */
  97. public removeColumnDefinition(index: number): Grid {
  98. if (index < 0 || index >= this._columnDefinitions.length) {
  99. return this;
  100. }
  101. for (var x = 0; x < this._rowDefinitions.length; x++) {
  102. let key = `${x}:${index}`;
  103. let cell = this._cells[key];
  104. this._removeCell(cell, key);
  105. }
  106. for (var x = 0; x < this._rowDefinitions.length; x++) {
  107. for (var y = index + 1; y < this._columnDefinitions.length; y++) {
  108. let previousKey = `${x}:${y - 1}`;
  109. let key = `${x}:${y}`;
  110. this._offsetCell(previousKey, key);
  111. }
  112. }
  113. this._columnDefinitions.splice(index, 1);
  114. this._markAsDirty();
  115. return this;
  116. }
  117. /**
  118. * Remove a row definition at specified index
  119. * @param index defines the index of the row to remove
  120. * @returns the current grid
  121. */
  122. public removeRowDefinition(index: number): Grid {
  123. if (index < 0 || index >= this._rowDefinitions.length) {
  124. return this;
  125. }
  126. for (var y = 0; y < this._columnDefinitions.length; y++) {
  127. let key = `${index}:${y}`;
  128. let cell = this._cells[key];
  129. this._removeCell(cell, key);
  130. }
  131. for (var y = 0; y < this._columnDefinitions.length; y++) {
  132. for (var x = index + 1; x < this._rowDefinitions.length; x++) {
  133. let previousKey = `${x - 1}:${y}`;
  134. let key = `${x}:${y}`;
  135. this._offsetCell(previousKey, key);
  136. }
  137. }
  138. this._rowDefinitions.splice(index, 1);
  139. this._markAsDirty();
  140. return this;
  141. }
  142. /**
  143. * Adds a new control to the current grid
  144. * @param control defines the control to add
  145. * @param row defines the row where to add the control (0 by default)
  146. * @param column defines the column where to add the control (0 by default)
  147. * @returns the current grid
  148. */
  149. public addControl(control: Control, row: number = 0, column: number = 0): Grid {
  150. if (this._rowDefinitions.length === 0) {
  151. // Add default row definition
  152. this.addRowDefinition(1, false);
  153. }
  154. if (this._columnDefinitions.length === 0) {
  155. // Add default column definition
  156. this.addColumnDefinition(1, false);
  157. }
  158. let x = Math.min(row, this._rowDefinitions.length - 1);
  159. let y = Math.min(column, this._columnDefinitions.length - 1);
  160. let key = `${x}:${y}`;
  161. let goodContainer = this._cells[key];
  162. if (!goodContainer) {
  163. goodContainer = new Container(key);
  164. this._cells[key] = goodContainer;
  165. goodContainer.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  166. goodContainer.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  167. super.addControl(goodContainer);
  168. }
  169. goodContainer.addControl(control);
  170. this._childControls.push(control);
  171. control._tag = key;
  172. this._markAsDirty();
  173. return this;
  174. }
  175. /**
  176. * Removes a control from the current container
  177. * @param control defines the control to remove
  178. * @returns the current container
  179. */
  180. public removeControl(control: Control): Container {
  181. var index = this._childControls.indexOf(control);
  182. if (index !== -1) {
  183. this._childControls.splice(index, 1);
  184. }
  185. let cell = this._cells[control._tag];
  186. if (cell) {
  187. cell.removeControl(control);
  188. }
  189. this._markAsDirty();
  190. return this;
  191. }
  192. /**
  193. * Creates a new Grid
  194. * @param name defines control name
  195. */
  196. constructor(public name?: string) {
  197. super(name);
  198. }
  199. protected _getTypeName(): string {
  200. return "Grid";
  201. }
  202. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  203. let widths = [];
  204. let heights = [];
  205. let lefts = [];
  206. let tops = [];
  207. let availableWidth = this._currentMeasure.width;
  208. let globalWidthPercentage = 0;
  209. let availableHeight = this._currentMeasure.height;
  210. let globalHeightPercentage = 0;
  211. // Heights
  212. let index = 0;
  213. for (var value of this._rowDefinitions) {
  214. if (value.isPixel) {
  215. let height = value.getValue(this._host);
  216. availableHeight -= height;
  217. heights[index] = height;
  218. } else {
  219. globalHeightPercentage += value.internalValue;
  220. }
  221. index++;
  222. }
  223. let top = 0;
  224. index = 0;
  225. for (var value of this._rowDefinitions) {
  226. tops.push(top);
  227. if (!value.isPixel) {
  228. let height = (value.internalValue / globalHeightPercentage) * availableHeight;
  229. top += height;
  230. heights[index] = height;
  231. } else {
  232. top += value.getValue(this._host);
  233. }
  234. index++;
  235. }
  236. // Widths
  237. index = 0;
  238. for (var value of this._columnDefinitions) {
  239. if (value.isPixel) {
  240. let width = value.getValue(this._host);
  241. availableWidth -= width;
  242. widths[index] = width;
  243. } else {
  244. globalWidthPercentage += value.internalValue;
  245. }
  246. index++;
  247. }
  248. let left = 0;
  249. index = 0;
  250. for (var value of this._columnDefinitions) {
  251. lefts.push(left);
  252. if (!value.isPixel) {
  253. let width = (value.internalValue / globalWidthPercentage) * availableWidth;
  254. left += width;
  255. widths[index] = width;
  256. } else {
  257. left += value.getValue(this._host);
  258. }
  259. index++;
  260. }
  261. // Setting child sizes
  262. for (var key in this._cells) {
  263. if (!this._cells.hasOwnProperty(key)) {
  264. continue;
  265. }
  266. let split = key.split(":");
  267. let x = parseInt(split[0]);
  268. let y = parseInt(split[1]);
  269. let cell = this._cells[key];
  270. cell.left = lefts[y] + "px";
  271. cell.top = tops[x] + "px";
  272. cell.width = widths[y] + "px";
  273. cell.height = heights[x] + "px";
  274. }
  275. super._additionalProcessing(parentMeasure, context);
  276. }
  277. /** Releases associated resources */
  278. public dispose() {
  279. super.dispose();
  280. for (var control of this._childControls) {
  281. control.dispose();
  282. }
  283. }
  284. }