scrollViewerWindow.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import { Measure } from "../../measure";
  2. import { Container } from "../container";
  3. import { ValueAndUnit } from "../../valueAndUnit";
  4. import { Control } from "../control";
  5. /**
  6. * Class used to hold a the container for ScrollViewer
  7. * @hidden
  8. */
  9. export class _ScrollViewerWindow extends Container {
  10. public parentClientWidth: number;
  11. public parentClientHeight: number;
  12. private _freezeControls = false;
  13. private _parentMeasure: Measure;
  14. private _oldLeft: number | null;
  15. private _oldTop: number | null;
  16. public get freezeControls(): boolean {
  17. return this._freezeControls;
  18. }
  19. public set freezeControls(value: boolean) {
  20. if (this._freezeControls === value) {
  21. return;
  22. }
  23. if (!value) {
  24. this._restoreMeasures();
  25. }
  26. // trigger a full normal layout calculation to be sure all children have their measures up to date
  27. this._freezeControls = false;
  28. var textureSize = this.host.getSize();
  29. var renderWidth = textureSize.width;
  30. var renderHeight = textureSize.height;
  31. var context = this.host.getContext();
  32. var measure = new Measure(0, 0, renderWidth, renderHeight);
  33. this.host._numLayoutCalls = 0;
  34. this.host._rootContainer._layout(measure, context);
  35. // in freeze mode, prepare children measures accordingly
  36. if (value) {
  37. this._updateMeasures();
  38. if (this._useBuckets()) {
  39. this._makeBuckets();
  40. }
  41. }
  42. this._freezeControls = value;
  43. this.host.markAsDirty(); // redraw with the (new) current settings
  44. }
  45. private _bucketWidth: number = 0;
  46. private _bucketHeight: number = 0;
  47. private _buckets: { [key: number]: Array<Control> } = {};
  48. private _bucketLen: number;
  49. public get bucketWidth(): number {
  50. return this._bucketWidth;
  51. }
  52. public get bucketHeight(): number {
  53. return this._bucketHeight;
  54. }
  55. public setBucketSizes(width: number, height: number): void {
  56. this._bucketWidth = width;
  57. this._bucketHeight = height;
  58. if (this._useBuckets()) {
  59. if (this._freezeControls) {
  60. this._makeBuckets();
  61. }
  62. } else {
  63. this._buckets = {};
  64. }
  65. }
  66. private _useBuckets(): boolean {
  67. return this._bucketWidth > 0 && this._bucketHeight > 0;
  68. }
  69. private _makeBuckets(): void {
  70. this._buckets = {};
  71. this._bucketLen = Math.ceil(this.widthInPixels / this._bucketWidth);
  72. this._dispatchInBuckets(this._children);
  73. this._oldLeft = null;
  74. this._oldTop = null;
  75. }
  76. private _dispatchInBuckets(children: Control[]): void {
  77. for (let i = 0; i < children.length; ++i) {
  78. let child = children[i];
  79. let bStartX = Math.max(0, Math.floor((child._customData._origLeft - this._customData.origLeft) / this._bucketWidth)),
  80. bEndX = Math.floor((child._customData._origLeft - this._customData.origLeft + child._currentMeasure.width - 1) / this._bucketWidth),
  81. bStartY = Math.max(0, Math.floor((child._customData._origTop - this._customData.origTop) / this._bucketHeight)),
  82. bEndY = Math.floor((child._customData._origTop - this._customData.origTop + child._currentMeasure.height - 1) / this._bucketHeight);
  83. while (bStartY <= bEndY) {
  84. for (let x = bStartX; x <= bEndX; ++x) {
  85. let bucket = bStartY * this._bucketLen + x,
  86. lstc = this._buckets[bucket];
  87. if (!lstc) {
  88. lstc = [];
  89. this._buckets[bucket] = lstc;
  90. }
  91. lstc.push(child);
  92. }
  93. bStartY++;
  94. }
  95. if (child instanceof Container && child._children.length > 0) {
  96. this._dispatchInBuckets(child._children);
  97. }
  98. }
  99. }
  100. // reset left and top measures for the window and all its children
  101. private _updateMeasures(): void {
  102. let left = this.leftInPixels | 0,
  103. top = this.topInPixels | 0;
  104. this._measureForChildren.left -= left;
  105. this._measureForChildren.top -= top;
  106. this._currentMeasure.left -= left;
  107. this._currentMeasure.top -= top;
  108. this._customData.origLeftForChildren = this._measureForChildren.left;
  109. this._customData.origTopForChildren = this._measureForChildren.top;
  110. this._customData.origLeft = this._currentMeasure.left;
  111. this._customData.origTop = this._currentMeasure.top;
  112. this._updateChildrenMeasures(this._children, left, top);
  113. }
  114. private _updateChildrenMeasures(children: Control[], left: number, top: number): void {
  115. for (let i = 0; i < children.length; ++i) {
  116. let child = children[i];
  117. child._currentMeasure.left -= left;
  118. child._currentMeasure.top -= top;
  119. child._customData._origLeft = child._currentMeasure.left; // save the original left and top values for each child
  120. child._customData._origTop = child._currentMeasure.top;
  121. if (child instanceof Container && child._children.length > 0) {
  122. this._updateChildrenMeasures(child._children, left, top);
  123. }
  124. }
  125. }
  126. private _restoreMeasures(): void {
  127. let left = this.leftInPixels | 0,
  128. top = this.topInPixels | 0;
  129. this._measureForChildren.left = this._customData.origLeftForChildren + left;
  130. this._measureForChildren.top = this._customData.origTopForChildren + top;
  131. this._currentMeasure.left = this._customData.origLeft + left;
  132. this._currentMeasure.top = this._customData.origTop + top;
  133. }
  134. /**
  135. * Creates a new ScrollViewerWindow
  136. * @param name of ScrollViewerWindow
  137. */
  138. constructor(name?: string) {
  139. super(name);
  140. }
  141. protected _getTypeName(): string {
  142. return "ScrollViewerWindow";
  143. }
  144. /** @hidden */
  145. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  146. super._additionalProcessing(parentMeasure, context);
  147. this._parentMeasure = parentMeasure;
  148. this._measureForChildren.left = this._currentMeasure.left;
  149. this._measureForChildren.top = this._currentMeasure.top;
  150. this._measureForChildren.width = parentMeasure.width;
  151. this._measureForChildren.height = parentMeasure.height;
  152. }
  153. /** @hidden */
  154. public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
  155. if (this._freezeControls) {
  156. this.invalidateRect(); // will trigger a redraw of the window
  157. return false;
  158. }
  159. return super._layout(parentMeasure, context);
  160. }
  161. private _scrollChildren(children: Control[], left: number, top: number): void {
  162. for (let i = 0; i < children.length; ++i) {
  163. let child = children[i];
  164. child._currentMeasure.left = child._customData._origLeft + left;
  165. child._currentMeasure.top = child._customData._origTop + top;
  166. child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
  167. if (child instanceof Container && child._children.length > 0) {
  168. this._scrollChildren(child._children, left, top);
  169. }
  170. }
  171. }
  172. private _scrollChildrenWithBuckets(left: number, top: number, scrollLeft: number, scrollTop: number): void {
  173. let bStartX = Math.max(0, Math.floor(-left / this._bucketWidth)),
  174. bEndX = Math.floor((-left + this._parentMeasure.width - 1) / this._bucketWidth),
  175. bStartY = Math.max(0, Math.floor(-top / this._bucketHeight)),
  176. bEndY = Math.floor((-top + this._parentMeasure.height - 1) / this._bucketHeight);
  177. while (bStartY <= bEndY) {
  178. for (let x = bStartX; x <= bEndX; ++x) {
  179. let bucket = bStartY * this._bucketLen + x,
  180. lstc = this._buckets[bucket];
  181. if (lstc) {
  182. for (let i = 0; i < lstc.length; ++i) {
  183. let child = lstc[i];
  184. child._currentMeasure.left = child._customData._origLeft + scrollLeft;
  185. child._currentMeasure.top = child._customData._origTop + scrollTop;
  186. child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
  187. }
  188. }
  189. }
  190. bStartY++;
  191. }
  192. }
  193. /** @hidden */
  194. public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Measure): void {
  195. if (!this._freezeControls) {
  196. super._draw(context, invalidatedRectangle);
  197. return;
  198. }
  199. this._localDraw(context);
  200. if (this.clipChildren) {
  201. this._clipForChildren(context);
  202. }
  203. let left = this.leftInPixels | 0,
  204. top = this.topInPixels | 0;
  205. if (this._useBuckets()) {
  206. if (this._oldLeft !== null && this._oldTop !== null) {
  207. this._scrollChildrenWithBuckets(this._oldLeft, this._oldTop, left, top);
  208. this._scrollChildrenWithBuckets(left, top, left, top);
  209. } else {
  210. this._scrollChildren(this._children, left, top);
  211. }
  212. } else {
  213. this._scrollChildren(this._children, left, top);
  214. }
  215. this._oldLeft = left;
  216. this._oldTop = top;
  217. for (var child of this._children) {
  218. if (!child._intersectsRect(this._parentMeasure)) {
  219. continue;
  220. }
  221. child._render(context, this._parentMeasure);
  222. }
  223. }
  224. protected _postMeasure(): void {
  225. if (this._freezeControls) {
  226. super._postMeasure();
  227. return;
  228. }
  229. var maxWidth = this.parentClientWidth;
  230. var maxHeight = this.parentClientHeight;
  231. for (var child of this.children) {
  232. if (!child.isVisible || child.notRenderable) {
  233. continue;
  234. }
  235. if (child.horizontalAlignment === Control.HORIZONTAL_ALIGNMENT_CENTER) {
  236. child._offsetLeft(this._currentMeasure.left - child._currentMeasure.left);
  237. }
  238. if (child.verticalAlignment === Control.VERTICAL_ALIGNMENT_CENTER) {
  239. child._offsetTop(this._currentMeasure.top - child._currentMeasure.top);
  240. }
  241. maxWidth = Math.max(maxWidth, child._currentMeasure.left - this._currentMeasure.left + child._currentMeasure.width + child.paddingRightInPixels);
  242. maxHeight = Math.max(maxHeight, child._currentMeasure.top - this._currentMeasure.top + child._currentMeasure.height + child.paddingBottomInPixels);
  243. }
  244. if (this._currentMeasure.width !== maxWidth) {
  245. this._width.updateInPlace(maxWidth, ValueAndUnit.UNITMODE_PIXEL);
  246. this._currentMeasure.width = maxWidth;
  247. this._rebuildLayout = true;
  248. this._isDirty = true;
  249. }
  250. if (this._currentMeasure.height !== maxHeight) {
  251. this._height.updateInPlace(maxHeight, ValueAndUnit.UNITMODE_PIXEL);
  252. this._currentMeasure.height = maxHeight;
  253. this._rebuildLayout = true;
  254. this._isDirty = true;
  255. }
  256. super._postMeasure();
  257. }
  258. }