|
@@ -11,6 +11,143 @@ export class _ScrollViewerWindow extends Container {
|
|
|
public parentClientWidth: number;
|
|
|
public parentClientHeight: number;
|
|
|
|
|
|
+ private _freezeControls = false;
|
|
|
+ private _parentMeasure: Measure;
|
|
|
+ private _oldLeft: number;
|
|
|
+ private _oldTop: number;
|
|
|
+
|
|
|
+ public get freezeControls(): boolean {
|
|
|
+ return this._freezeControls;
|
|
|
+ }
|
|
|
+
|
|
|
+ public set freezeControls(value: boolean) {
|
|
|
+ if (this._freezeControls === value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // trigger a full normal layout calculation to be sure all children have their measures up to date
|
|
|
+ this._freezeControls = false;
|
|
|
+
|
|
|
+ var textureSize = this.host.getSize();
|
|
|
+ var renderWidth = textureSize.width;
|
|
|
+ var renderHeight = textureSize.height;
|
|
|
+
|
|
|
+ var context = this.host.getContext();
|
|
|
+
|
|
|
+ var measure = new Measure(0, 0, renderWidth, renderHeight);
|
|
|
+
|
|
|
+ Control.NumLayoutCalls = 0;
|
|
|
+
|
|
|
+ this.host._rootContainer._layout(measure, context);
|
|
|
+
|
|
|
+ // in freeze mode, prepare children measures accordingly
|
|
|
+ if (value) {
|
|
|
+ this._updateMeasures();
|
|
|
+ if (this._useBuckets()) {
|
|
|
+ this._makeBuckets();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this._freezeControls = value;
|
|
|
+
|
|
|
+ this.host.markAsDirty(); // redraw with the (new) current settings
|
|
|
+ }
|
|
|
+
|
|
|
+ private _bucketWidth: number = 0;
|
|
|
+ private _bucketHeight: number = 0;
|
|
|
+ private _buckets: { [key: number]: Array<Control> } = {};
|
|
|
+ private _bucketLen: number;
|
|
|
+
|
|
|
+ public get bucketWidth(): number {
|
|
|
+ return this._bucketWidth;
|
|
|
+ }
|
|
|
+
|
|
|
+ public get bucketHeight(): number {
|
|
|
+ return this._bucketHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ public setBucketSizes(width: number, height: number): void {
|
|
|
+ this._bucketWidth = width;
|
|
|
+ this._bucketHeight = height;
|
|
|
+
|
|
|
+ if (this._useBuckets()) {
|
|
|
+ if (this._freezeControls) {
|
|
|
+ this._makeBuckets();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this._buckets = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private _useBuckets(): boolean {
|
|
|
+ return this._bucketWidth > 0 && this._bucketHeight > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private _makeBuckets(): void {
|
|
|
+ this._buckets = {};
|
|
|
+ this._bucketLen = Math.ceil(this.widthInPixels / this._bucketWidth);
|
|
|
+ this._dispatchInBuckets(this._children);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _dispatchInBuckets(children: Control[]): void {
|
|
|
+ for (let i = 0; i < children.length; ++i) {
|
|
|
+ let child = children[i];
|
|
|
+
|
|
|
+ let bStartX = Math.max(0, Math.floor((child._currentMeasure.left - this._currentMeasure.left) / this._bucketWidth)),
|
|
|
+ bEndX = Math.floor((child._currentMeasure.left - this._currentMeasure.left + child._currentMeasure.width - 1) / this._bucketWidth),
|
|
|
+ bStartY = Math.max(0, Math.floor((child._currentMeasure.top - this._currentMeasure.top) / this._bucketHeight)),
|
|
|
+ bEndY = Math.floor((child._currentMeasure.top - this._currentMeasure.top + child._currentMeasure.height - 1) / this._bucketHeight);
|
|
|
+
|
|
|
+ while (bStartY <= bEndY) {
|
|
|
+ for (let x = bStartX; x <= bEndX; ++x) {
|
|
|
+ let bucket = bStartY * this._bucketLen + x,
|
|
|
+ lstc = this._buckets[bucket];
|
|
|
+
|
|
|
+ if (!lstc) {
|
|
|
+ lstc = [];
|
|
|
+ this._buckets[bucket] = lstc;
|
|
|
+ }
|
|
|
+
|
|
|
+ lstc.push(child);
|
|
|
+ }
|
|
|
+ bStartY++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (child instanceof Container && child._children.length > 0) {
|
|
|
+ this._dispatchInBuckets(child._children);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // reset left and top measures for the window and all its children
|
|
|
+ private _updateMeasures(): void {
|
|
|
+ let left = this.leftInPixels | 0,
|
|
|
+ top = this.topInPixels | 0;
|
|
|
+
|
|
|
+ this._measureForChildren.left -= left;
|
|
|
+ this._measureForChildren.top -= top;
|
|
|
+ this._currentMeasure.left -= left;
|
|
|
+ this._currentMeasure.top -= top;
|
|
|
+
|
|
|
+ this._updateChildrenMeasures(this._children, left, top);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _updateChildrenMeasures(children: Control[], left: number, top: number): void {
|
|
|
+ for (let i = 0; i < children.length; ++i) {
|
|
|
+ let child = children[i];
|
|
|
+
|
|
|
+ child._currentMeasure.left -= left;
|
|
|
+ child._currentMeasure.top -= top;
|
|
|
+
|
|
|
+ child._customData._origLeft = child._currentMeasure.left; // save the original left and top values for each child
|
|
|
+ child._customData._origTop = child._currentMeasure.top;
|
|
|
+
|
|
|
+ if (child instanceof Container && child._children.length > 0) {
|
|
|
+ this._updateChildrenMeasures(child._children, left, top);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Creates a new ScrollViewerWindow
|
|
|
* @param name of ScrollViewerWindow
|
|
@@ -27,6 +164,8 @@ export class _ScrollViewerWindow extends Container {
|
|
|
protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
|
|
|
super._additionalProcessing(parentMeasure, context);
|
|
|
|
|
|
+ this._parentMeasure = parentMeasure;
|
|
|
+
|
|
|
this._measureForChildren.left = this._currentMeasure.left;
|
|
|
this._measureForChildren.top = this._currentMeasure.top;
|
|
|
|
|
@@ -34,7 +173,94 @@ export class _ScrollViewerWindow extends Container {
|
|
|
this._measureForChildren.height = parentMeasure.height;
|
|
|
}
|
|
|
|
|
|
+ /** @hidden */
|
|
|
+ public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
|
|
|
+ if (this._freezeControls) {
|
|
|
+ this.invalidateRect(); // will trigger a redraw of the window
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return super._layout(parentMeasure, context);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _scrollChildren(children: Control[], left: number, top: number): void {
|
|
|
+ for (let i = 0; i < children.length; ++i) {
|
|
|
+ let child = children[i];
|
|
|
+
|
|
|
+ child._currentMeasure.left = child._customData._origLeft + left;
|
|
|
+ child._currentMeasure.top = child._customData._origTop + top;
|
|
|
+ child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
|
|
|
+
|
|
|
+ if (child instanceof Container && child._children.length > 0) {
|
|
|
+ this._scrollChildren(child._children, left, top);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private _scrollChildrenWithBuckets(left: number, top: number, scrollLeft: number, scrollTop: number): void {
|
|
|
+ let bStartX = Math.max(0, Math.floor(-left / this._bucketWidth)),
|
|
|
+ bEndX = Math.floor((-left + this._parentMeasure.width - 1) / this._bucketWidth),
|
|
|
+ bStartY = Math.max(0, Math.floor(-top / this._bucketHeight)),
|
|
|
+ bEndY = Math.floor((-top + this._parentMeasure.height - 1) / this._bucketHeight);
|
|
|
+
|
|
|
+ while (bStartY <= bEndY) {
|
|
|
+ for (let x = bStartX; x <= bEndX; ++x) {
|
|
|
+ let bucket = bStartY * this._bucketLen + x,
|
|
|
+ lstc = this._buckets[bucket];
|
|
|
+
|
|
|
+ if (lstc) {
|
|
|
+ for (let i = 0; i < lstc.length; ++i) {
|
|
|
+ let child = lstc[i];
|
|
|
+ child._currentMeasure.left = child._customData._origLeft + scrollLeft;
|
|
|
+ child._currentMeasure.top = child._customData._origTop + scrollTop;
|
|
|
+ child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ bStartY++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @hidden */
|
|
|
+ public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Measure): void {
|
|
|
+ if (!this._freezeControls) {
|
|
|
+ super._draw(context, invalidatedRectangle);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._localDraw(context);
|
|
|
+
|
|
|
+ if (this.clipChildren) {
|
|
|
+ this._clipForChildren(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ let left = this.leftInPixels,
|
|
|
+ top = this.topInPixels;
|
|
|
+
|
|
|
+ if (this._useBuckets()) {
|
|
|
+ this._scrollChildrenWithBuckets(this._oldLeft, this._oldTop, left, top);
|
|
|
+ this._scrollChildrenWithBuckets(left, top, left, top);
|
|
|
+ } else {
|
|
|
+ this._scrollChildren(this._children, left, top);
|
|
|
+ }
|
|
|
+
|
|
|
+ this._oldLeft = left;
|
|
|
+ this._oldTop = top;
|
|
|
+
|
|
|
+ for (var child of this._children) {
|
|
|
+ if (!child._intersectsRect(this._parentMeasure)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ child._render(context, this._parentMeasure);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
protected _postMeasure(): void {
|
|
|
+ if (this._freezeControls) {
|
|
|
+ super._postMeasure();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
var maxWidth = this.parentClientWidth;
|
|
|
var maxHeight = this.parentClientHeight;
|
|
|
for (var child of this.children) {
|