• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import Matrix4 from '@ohos.matrix4';
17import { MediaDataItem } from '@ohos/base/src/main/ets/data/MediaDataItem';
18import { Log } from '@ohos/base/src/main/ets/utils/Log';
19import { Broadcast } from '@ohos/base/src/main/ets/utils/Broadcast';
20import { clamp } from '@ohos/base/src/main/ets/utils/MathUtil';
21import { Constants } from '../constants/Constants';
22import screenManager from '@ohos/base/src/main/ets/manager/ScreenManager';
23import { MediaConstants } from '@ohos/base/src/main/ets/constants/MediaConstants';
24
25const TAG = "EventPipeline"
26
27export class EventPipeline {
28
29    // last offset
30    private lastOffset: [number, number] = [0, 0];
31
32    // offset
33    private offset: [number, number] = [0, 0];
34
35    // default scale
36    private defaultScale = 1.0;
37
38    // last scale
39    private lastScale = 1.0;
40
41    // scale
42    private scale = 1.0;
43
44    // the zoom center point is a percentage position relative to the control, not an absolute position
45    private center: [number, number] = [Constants.CENTER_DEFAULT, Constants.CENTER_DEFAULT];
46
47    // leftmost zoom Center,(1 - leftMost)is rightmost zoom Center
48    private leftMost = 0.0;
49
50    // top zoom center,(1 - topMost)is bottom zoom center
51    private topMost = 0.0;
52
53    // double tap scale
54    private doubleTapScale = 1.0;
55
56    // max scale
57    private maxScale = 1.0;
58
59    // has reached the far left
60    private hasReachLeft = true;
61
62    // has reached the far right
63    private hasReachRight = true;
64
65    // has reached the far top
66    private hasReachTop = true;
67
68    // has reached the far bottom
69    private hasReachBottom = true;
70
71    // Broadcast
72    private broadCast: Broadcast;
73
74    // item
75    private item: MediaDataItem;
76
77    // timeStamp
78    private timeStamp: string;
79
80    // width
81    private width: number;
82
83    // height
84    private height: number;
85
86    // Large display control width
87    private componentWidth = vp2px(screenManager.getWinWidth());
88
89    // Large display control height
90    private componentHeight = vp2px(screenManager.getWinHeight());
91
92    // is now in animation
93    private isInAnimation = false;
94
95    // pull down to return flag to prevent multiple triggers
96    private isExiting = false;
97
98		private updateMatrix: Function;
99
100    constructor(broadCastParam: Broadcast, item: MediaDataItem, timeStamp: string, updateMatrix: Function) {
101        this.broadCast = broadCastParam;
102        this.item = item;
103        this.timeStamp = timeStamp;
104				this.updateMatrix = updateMatrix;
105        this.width = this.item.imgWidth == 0 ? MediaConstants.DEFAULT_SIZE : this.item.imgWidth;
106        this.height = this.item.imgHeight == 0 ? MediaConstants.DEFAULT_SIZE : this.item.imgHeight;
107        this.evaluateScales();
108    }
109
110    onDataChanged(item: MediaDataItem) {
111        this.item = item;
112        this.width = this.item.imgWidth == 0 ? MediaConstants.DEFAULT_SIZE : this.item.imgWidth;
113        this.height = this.item.imgHeight == 0 ? MediaConstants.DEFAULT_SIZE : this.item.imgHeight;
114        this.evaluateScales();
115    }
116
117    setDefaultScale(scale) {
118        this.defaultScale = scale;
119        this.lastScale = scale;
120    }
121
122    onComponentSizeChanged() {
123        this.evaluateScales();
124    }
125
126    onTouch(event: TouchEvent) {
127        Log.debug(TAG, `onTouch trigger: ${event.type}, ${[this.isInAnimation, this.isExiting]}`);
128        if (this.isInAnimation || this.isExiting) {
129            return;
130        }
131        if (event.type == TouchType.Down || event.type == TouchType.Up) {
132            this.emitDirectionChange();
133        }
134
135        if (event.type == TouchType.Up) {
136            this.lastOffset = this.evaluateOffset();
137            this.lastScale = this.lastScale * this.scale;
138            this.scale = 1;
139            this.offset = [0, 0];
140        }
141    }
142
143    private emitDirectionChange(): void {
144
145        /**
146         * reachLeft reachRight scale>1,only five possible situations(when scale<=1,reachLeft、reachRight is true):
147         * T T T:Vertical
148         * T T F:Vertical(initial state)
149         * T F T:Vertical | Left
150         * F T T:Vertical | Right
151         * F F T:All
152         */
153        let direction;
154        let scale = this.lastScale * this.scale;
155        let isEnlarged = Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS));
156        if (!this.hasReachLeft && !this.hasReachRight && isEnlarged) {
157            direction = PanDirection.All;
158        } else if (!this.hasReachLeft && this.hasReachRight && isEnlarged) {
159            direction = PanDirection.Vertical | PanDirection.Right;
160        } else if (this.hasReachLeft && !this.hasReachRight && isEnlarged) {
161            direction = PanDirection.Vertical | PanDirection.Left;
162        } else {
163            direction = PanDirection.Vertical;
164        }
165
166        Log.info(TAG, `emitDirectionChange reaches: ${[this.hasReachLeft, this.hasReachRight, this.hasReachTop, this.hasReachBottom]}, scale ${scale}, direction: ${direction}`);
167        if (this.isExiting) {
168            return;
169        }
170
171        if (direction == PanDirection.Vertical || direction == (PanDirection.Vertical | PanDirection.Left) ||
172        direction == (PanDirection.Vertical | PanDirection.Right)) {
173            this.broadCast.emit(Constants.SET_DISABLE_SWIPE, [false]);
174        } else {
175            this.broadCast.emit(Constants.SET_DISABLE_SWIPE, [true]);
176        }
177        this.broadCast.emit(Constants.DIRECTION_CHANGE + this.item.uri + this.timeStamp, [direction]);
178    }
179
180    private evaluateOffset(): [number, number] {
181        Log.info(TAG, `evaluateOffset lastOffset: ${this.lastOffset}, offset: ${this.offset}`);
182        let centerX = (this.center[0] - Constants.CENTER_DEFAULT) * this.componentWidth * (this.defaultScale - this.scale) * this.lastScale;
183        let centerY = (this.center[1] - Constants.CENTER_DEFAULT) * this.componentHeight * (this.defaultScale - this.scale) * this.lastScale;
184        let offsetX = this.lastOffset[0] + this.offset[0] + centerX;
185        let offsetY = this.lastOffset[1] + this.offset[1] + centerY;
186        Log.debug(TAG, `evaluateOffset offsetX: ${offsetX}, offsetY: ${offsetY}`);
187        return [offsetX, offsetY];
188    }
189
190    private emitTouchEvent(): void {
191        let offset: [number, number];
192        let scale = this.lastScale * this.scale;
193        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
194            let limits = this.evaluateOffsetRange(scale);
195            offset = this.evaluateOffset();
196            // the offset in the X direction is always limited for non shrinking scenes
197            offset[0] = clamp(offset[0], limits[0], limits[1]);
198            if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
199                // cannot pull down to return, limit y
200                offset[1] = clamp(offset[1], limits[2], limits[3]);
201            } else {
202                // can pull down to return to the scene, and only limit y to drag upward, limit the lower bound
203                offset[1] = Math.max(limits[2], offset[1]);
204            }
205        } else {
206            // When zooming in, adjust the zoom center to the display center point
207            offset = [0, 0];
208        }
209        let moveX = offset[0];
210        let moveY = offset[1];
211        Log.debug(TAG, `emitTouchEvent moveX: ${moveX}, moveY: ${moveY}`);
212        let matrix = Matrix4.identity()
213            .scale({
214                x: scale,
215                y: scale,
216            })
217            .translate({
218                x: moveX,
219                y: moveY
220            })
221            .copy();
222        Log.debug(TAG, `emitTouchEvent lastOffset: ${this.lastOffset}, offset: ${this.offset},\
223        center: ${this.center}, scale: ${[this.lastScale, this.scale]}`);
224				this.updateMatrix(matrix);
225        this.evaluateBounds();
226    }
227
228    private evaluateScales(): void {
229        if (this.width * this.componentHeight < this.componentWidth * this.height) {
230            // The aspect ratio is less than the display aspect ratio of the control
231            // the height of the control is equal to the height of the picture
232            this.maxScale = this.height / this.componentHeight;
233            // Double click the enlarged scale to ensure that the left and right boundaries are filled
234            this.doubleTapScale = this.componentWidth * this.height / this.width / this.componentHeight;
235            // leftMost = (1 - dspW / compW) / 2 = (1 - compH * imgW / imgH / compW) / 2
236            this.leftMost = (1 - this.componentHeight * this.width / this.height / this.componentWidth) / Constants.NUMBER_2;
237            this.topMost = 0.0;
238        } else if (this.width * this.componentHeight == this.componentWidth * this.height) {
239            // The aspect ratio is equal to the display aspect ratio of the control
240            this.doubleTapScale = Constants.SAME_RATIO_SCALE_FACTOR;
241            this.maxScale = this.doubleTapScale * Constants.MAX_SCALE_EXTRA_FACTOR;
242            this.leftMost = 0;
243            this.topMost = 0;
244        } else {
245            // The aspect ratio is greater than the display aspect ratio of the control
246            // the width of the control is equal to the width of the picture
247            this.maxScale = this.width / this.componentWidth;
248            // Double click the enlarged scale to ensure that the top and bottom fill the boundary
249            this.doubleTapScale = this.componentHeight * this.width / this.height / this.componentWidth;
250            this.leftMost = 0.0;
251            this.topMost = (1 - this.componentWidth * this.height / this.width / this.componentHeight) / Constants.NUMBER_2;
252        }
253
254        this.maxScale = Math.max(this.maxScale, Constants.COMPONENT_SCALE_CEIL);
255        if (this.doubleTapScale > this.maxScale) {
256            this.maxScale = this.doubleTapScale * Constants.MAX_SCALE_EXTRA_FACTOR;
257        }
258        Log.debug(TAG, `evaluateScales: ${this.width}*${this.height} &\
259        ${this.componentWidth}*${this.componentHeight},\
260        max: ${this.maxScale}, most: [${this.leftMost},${this.topMost}], double: ${this.doubleTapScale}`);
261    }
262
263    private evaluateCompBounds(): [number, number] {
264        let scale = this.lastScale * this.scale;
265        let offset = this.evaluateOffset();
266        let result: [number, number] = [
267            offset[0] - this.componentWidth * (Number(scale.toFixed(Constants.RESERVED_DIGITS)) - Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) / Constants.NUMBER_2,
268            offset[1] - this.componentHeight * (Number(scale.toFixed(Constants.RESERVED_DIGITS)) - Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) / Constants.NUMBER_2
269        ];
270        Log.debug(TAG, `evaluateCompBounds: ${result}`);
271        return result;
272    }
273
274    private evaluateImgDisplaySize(): [number, number] {
275        let screenScale = 1;
276        let widthScale = this.componentWidth / this.item.imgWidth;
277        let heightScale = this.componentHeight / this.item.imgHeight;
278        screenScale = widthScale > heightScale ? heightScale : widthScale;
279        let scale = this.lastScale * this.scale * screenScale;
280        let imgDisplayWidth = 0;
281        let imgDisplayHeight = 0;
282        imgDisplayWidth = this.width * scale;
283        imgDisplayHeight = this.height * scale;
284        return [imgDisplayWidth, imgDisplayHeight];
285    }
286
287    private evaluateImgDisplayBounds(): [number, number] {
288        // For the left boundary of the component,
289        // the offset caused by amplification is - compw * (scale-1) / 2,
290        // plus the offset of the gesture to obtain the left boundary of the control.
291        // The same is true for the upper boundary
292        let scale = this.lastScale * this.scale;
293        let leftTop = this.evaluateCompBounds();
294        let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
295        let imgDisplayWidth = imgDisplaySize[0];
296        let imgDisplayHeight = imgDisplaySize[1];
297        let imgLeftBound = 0;
298        let imgTopBound = 0;
299        if (this.width / this.height > this.componentWidth / this.componentHeight) {
300            imgLeftBound = leftTop[0];
301            imgTopBound = leftTop[1] + (this.componentHeight * scale - imgDisplayHeight) / Constants.NUMBER_2;
302        } else {
303            // Control width minus the picture width, divided by 2,
304            // you can get the distance from the left of the picture to the left of the control.
305            // Plus offsetX is the left boundary of the picture currently displayed
306            imgLeftBound = (this.componentWidth * scale - imgDisplayWidth) / Constants.NUMBER_2 + leftTop[0];
307            imgTopBound = leftTop[1];
308        }
309        return [imgLeftBound, imgTopBound];
310    }
311
312    // Calculate picture display boundary
313    private evaluateBounds(): void {
314        let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
315        let imgDisplayWidth = imgDisplaySize[0];
316
317        let imgDisplayBounds = this.evaluateImgDisplayBounds();
318        let imgLeftBound = imgDisplayBounds[0];
319        this.hasReachLeft = imgLeftBound > -1;
320        this.hasReachRight = imgLeftBound + imgDisplayWidth < this.componentWidth + 1;
321    }
322
323    /**
324     * Calculate the upper and lower bounds of offset in X and Y directions under the current scale
325     *
326     * @param scale The display magnification of the current control, usually this.lastScale * this.scale
327     * @returns 0&1 X-direction offset lower & upper bound, 2&3 Y-direction offset lower & upper bound
328     */
329    private evaluateOffsetRange(scale: number): [number, number, number, number] {
330        let result: [number, number, number, number] = [0, 0, 0, 0];
331        let screenScale = 1;
332        let widthScale = this.componentWidth / this.item.imgWidth;
333        let heightScale = this.componentHeight / this.item.imgHeight;
334        screenScale = widthScale > heightScale ? heightScale : widthScale;
335        let left = (screenScale * scale * this.width - this.componentWidth) / Constants.NUMBER_2;
336        let top = (screenScale * scale * this.height - this.componentHeight) / Constants.NUMBER_2;
337        top = Math.max(top, 0);
338        left = Math.max(left, 0);
339        result = [-left, left, -top, top];
340        Log.debug(TAG, `evaluateOffsetRange scale: ${scale}, defaultScale: ${this.defaultScale}, result: ${result}`);
341        return result;
342    }
343
344    private emitPullDownToBackEvent(): void {
345        Log.debug(TAG, 'emitPullDownToBackEvent');
346        if (this.isExiting) {
347            Log.info(TAG, `emitPullDownToBack isExiting: ${this.isExiting}`);
348            return;
349        }
350        this.broadCast.emit(Constants.PULL_DOWN_END, []);
351        this.isExiting = true;
352    }
353
354    private emitPullDownCancelEvent(): void {
355        Log.debug(TAG, 'emitPullDownCancelEvent');
356        this.broadCast.emit(Constants.PULL_DOWN_CANCEL, []);
357    }
358
359    onMoveStart(offsetX: number, offsetY: number) {
360        if (this.isInAnimation || this.isExiting) {
361            return;
362        }
363        // Reset offset at the beginning of dragging to prevent jumping
364        this.offset = [0, 0];
365        this.evaluateBounds();
366        let scale = this.lastScale * this.scale;
367        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
368            // Hide bars with zoom drag
369            this.broadCast.emit(Constants.HIDE_BARS, []);
370        }
371        if (scale.toFixed(Constants.RESERVED_DIGITS) == this.defaultScale.toFixed(Constants.RESERVED_DIGITS) && offsetY > 0) {
372            // Drop down return to hide details first
373            this.broadCast.emit(Constants.PULL_DOWN_START, []);
374        }
375    }
376
377    /**
378     * Each callback returns the displacement relative to the start point of the gesture
379     *
380     * @param offsetX offsetX
381     * @param offsetY offsetY
382     */
383    onMove(offsetX: number, offsetY: number) {
384        if (this.isInAnimation || this.isExiting) {
385            return;
386        }
387        let scale = this.lastScale * this.scale;
388        let limits = this.evaluateOffsetRange(scale);
389        let measureX = this.lastOffset[0] + (this.center[0] - Constants.CENTER_DEFAULT) * this.componentWidth
390        * (this.defaultScale - this.scale) * this.lastScale;
391        let measureY = this.lastOffset[1] + (this.center[1] - Constants.CENTER_DEFAULT) * this.componentHeight
392        * (this.defaultScale - this.scale) * this.lastScale;
393        let moveX = offsetX;
394        let moveY = offsetY;
395        let offX = measureX + moveX;
396        let offY = measureY + moveY;
397        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
398            // The offset in the X direction is always limited for non shrinking scenes
399            offX = clamp(offX, limits[0], limits[1]);
400            if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
401                // cannot drop down to return to the scene, limit y
402                offY = clamp(offY, limits[Constants.NUMBER_2], limits[Constants.NUMBER_3]);
403            } else {
404                // pull down to return to the scene, and only limit y to drag upward, that is, limit the lower bound
405                offY = Math.max(limits[Constants.NUMBER_2], offY);
406            }
407        }
408        let tmpX = offX - measureX;
409        let tmpY = offY - measureY;
410        this.offset = [tmpX, tmpY];
411        this.emitTouchEvent();
412    }
413
414    onMoveEnd(offsetX, offsetY) {
415        if (this.isInAnimation || this.isExiting) {
416            return;
417        }
418        let scale = this.lastScale * this.scale;
419        Log.debug(TAG, `onMoveEnd: scale is ${scale} offsetY is ${offsetY}`);
420        if (scale.toFixed(Constants.RESERVED_DIGITS) == this.defaultScale.toFixed(Constants.RESERVED_DIGITS) && offsetY > Constants.PULL_DOWN_THRESHOLD) {
421            this.emitPullDownToBackEvent();
422        } else if (scale.toFixed(Constants.RESERVED_DIGITS) == this.defaultScale.toFixed(Constants.RESERVED_DIGITS)) {
423            // The reset animation is triggered when the threshold is not reached
424            this.startAnimation(Matrix4.identity().scale({
425                x: this.defaultScale,
426                y: this.defaultScale
427            }).copy());
428            this.emitPullDownCancelEvent();
429        } else {
430            this.emitDirectionChange();
431        }
432    }
433
434    onScaleStart(scale: number, centerX: number, centerY: number) {
435        Log.info(TAG, `onScaleStart: ${[this.isInAnimation, this.isExiting]}`);
436        if (this.isInAnimation || this.isExiting) {
437            return;
438        }
439        this.scale = 1;
440        this.evaluateBounds();
441        // Adjust action bar status
442        this.broadCast.emit(Constants.HIDE_BARS, []);
443        this.center = this.evaluateCenter(centerX, centerY);
444    }
445
446    /**
447     * Calculates the percentage position of the current zoom center relative to the control
448     *
449     * @param centerX The absolute position of the touch point on the screen
450     * @param centerY The absolute position of the touch point on the screen
451     * @returns The percentage position of the current zoom center relative to the control
452     */
453    private evaluateCenter(centerX: number, centerY: number): [number, number] {
454        // Calculate the coordinates of the upper left corner of the control relative to
455        // the upper left corner of the current display
456        let scale = this.lastScale * this.scale;
457        let leftTop = this.evaluateCompBounds();
458
459        // Get the touch coordinates relative to the control
460        let cxRelativeToComp = clamp((centerX - leftTop[0])
461        / (this.componentWidth * scale), this.leftMost, 1 - this.leftMost);
462        let cyRelativeToComp = clamp((centerY - leftTop[1])
463        / (this.componentHeight * scale), this.topMost, 1 - this.topMost);
464
465        let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
466        let imgDisplayWidth = imgDisplaySize[0];
467        let imgDisplayHeight = imgDisplaySize[1];
468
469        let imgDisplayBounds = this.evaluateImgDisplayBounds();
470        let imgLeftBound = imgDisplayBounds[0];
471        let imgTopBound = imgDisplayBounds[1];
472
473        // When the touch center point is outside the picture display area, take the midpoint
474        if (this.width / this.height > this.componentWidth / this.componentHeight) {
475            if (centerY < imgTopBound || centerY > imgTopBound + imgDisplayHeight) {
476                cyRelativeToComp = Constants.CENTER_DEFAULT;
477            }
478        } else {
479            if (centerX < imgLeftBound || centerX > imgLeftBound + imgDisplayWidth) {
480                cxRelativeToComp = Constants.CENTER_DEFAULT;
481            }
482        }
483
484        // Calculate the percentage of the center point of the touch
485        let center: [number, number] = [cxRelativeToComp, cyRelativeToComp];
486        Log.debug(TAG, `evaluateCenter center: ${center}, ${[centerX, centerY]},\
487        size: ${imgDisplaySize}, bounds: ${imgDisplayBounds}, leftTop: ${leftTop},\
488        compSize: ${[this.componentWidth * scale, this.componentHeight * scale]}`);
489        return center;
490    }
491
492    onScale(scale: number) {
493        Log.debug(TAG, `onScale: ${[this.isInAnimation, this.isExiting]}, scale: ${scale}`);
494        if (this.isInAnimation || this.isExiting) {
495            return;
496        }
497        this.evaluateBounds();
498        this.scale = scale;
499        if (this.lastScale * scale <= Constants.COMPONENT_SCALE_FLOOR) {
500            this.scale = Constants.COMPONENT_SCALE_FLOOR / this.lastScale;
501        }
502        if (this.lastScale * scale >= this.maxScale * Constants.OVER_SCALE_EXTRA_FACTOR) {
503            this.scale = this.maxScale * Constants.OVER_SCALE_EXTRA_FACTOR / this.lastScale;
504        }
505        this.emitTouchEvent();
506    }
507
508    onScaleEnd() {
509        Log.info(TAG, `onScaleEnd: ${[this.isInAnimation, this.isExiting]}`);
510        if (this.isInAnimation || this.isExiting) {
511            return;
512        }
513        this.evaluateBounds();
514        let scale = this.lastScale * this.scale;
515        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) >= Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS)) && scale <= this.maxScale) {
516            Log.info(TAG, `does not need to do animation: ${scale}`);
517            this.emitDirectionChange();
518            return;
519        }
520        let animationEndMatrix: any = null;
521        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) <= Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
522            // Zoom out too small to trigger the restored animation
523            animationEndMatrix = Matrix4.identity().scale({
524                x: this.defaultScale,
525                y: this.defaultScale
526            }).copy();
527        } else {
528            // Do the animation of retracting maxScale when zooming in
529            animationEndMatrix = this.evaluateAnimeMatrix(this.maxScale, this.center);
530        }
531        this.startAnimation(animationEndMatrix);
532    }
533
534    private evaluateAnimeMatrix(scale: number, center: [number, number]): any {
535        let offset = [
536            this.lastOffset[0] + this.offset[0] + (center[0] - Constants.CENTER_DEFAULT) * this.componentWidth
537            * (this.defaultScale - scale / this.lastScale) * this.lastScale,
538            this.lastOffset[1] + this.offset[1] + (center[1] - Constants.CENTER_DEFAULT) * this.componentHeight
539            * (this.defaultScale - scale / this.lastScale) * this.lastScale
540        ];
541        if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
542            let limits = this.evaluateOffsetRange(scale);
543            // The offset in the X direction is always limited for non shrinking scenes
544            offset[0] = clamp(offset[0], limits[0], limits[1]);
545            if (Number(scale.toFixed(Constants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
546                // Cannot drop down to return to the scene, limit y
547                offset[1] = clamp(offset[1], limits[Constants.NUMBER_2], limits[Constants.NUMBER_3]);
548            } else {
549                // You can pull down to return to the scene, and only limit y to drag upward,
550                // that is, limit the lower bound
551                offset[1] = Math.max(limits[Constants.NUMBER_2], offset[1]);
552            }
553        } else {
554            // When zooming in, adjust the zoom center to the display center point
555            offset = [0, 0];
556        }
557        let animationEndMatrix = Matrix4.identity()
558            .copy()
559            .scale({
560                x: scale,
561                y: scale,
562            })
563            .translate({
564                x: offset[0],
565                y: offset[1]
566            })
567            .copy();
568        Log.debug(TAG, `evaluateAnimeMatrix scale:${scale}, center:${center}`);
569        return animationEndMatrix;
570    }
571
572    /**
573     * Double click to trigger zoom.
574     * If the current scale is less than or equal to 1, zoom to doubleTapScale;
575     * If the current scale is greater than 1, scale to 1;
576     *
577     * @param centerX the location of double click
578     * @param centerY the location of double click
579     */
580    onDoubleTap(centerX: number, centerY: number) {
581        if (this.isInAnimation || this.isExiting) {
582            Log.debug(TAG, `onDoubleTap not avaliable: ${[this.isInAnimation, this.isExiting]}`);
583            return;
584        }
585        // Adjust action bar status
586        this.broadCast.emit(Constants.HIDE_BARS, []);
587        let matrix;
588        Log.debug(TAG, `onDoubleTap lastScale: ${this.lastScale}, scale: ${this.scale}, defaultScale: ${this.defaultScale}`);
589        if (Number(this.lastScale.toFixed(Constants.RESERVED_DIGITS)) * this.scale > Number(this.defaultScale.toFixed(Constants.RESERVED_DIGITS))) {
590            // Scale to original state when scale is greater than 1
591            matrix = Matrix4.identity().scale({
592                x: this.defaultScale,
593                y: this.defaultScale
594            }).copy();
595        } else {
596            // The zoom in status calculates the zoom in center according to the click position
597            let center = this.evaluateCenter(centerX, centerY);
598            // When the picture aspect ratio is less than the control aspect ratio,
599            // centerX is set to 0.5,
600            // whereas centerY is set to 0.5 to ensure that
601            // the short side is close to the side after double clicking and enlarging
602            if (this.width / this.height < this.componentWidth / this.componentHeight) {
603                center = [Constants.CENTER_DEFAULT, center[1]];
604            } else {
605                center = [center[0], Constants.CENTER_DEFAULT];
606            }
607            matrix = this.evaluateAnimeMatrix(this.doubleTapScale * this.defaultScale, center);
608        }
609        Log.debug(TAG, `onDoubleTap matrix: ${matrix.matrix4x4}`);
610        this.startAnimation(matrix);
611    }
612
613    reset() {
614        this.lastOffset = [0, 0];
615        this.offset = [0, 0];
616        this.lastScale = 1.0;
617        this.scale = 1;
618        this.hasReachLeft = true;
619        this.hasReachRight = true;
620        this.hasReachTop = true;
621        this.hasReachBottom = true;
622        this.isInAnimation = false;
623        this.isExiting = false;
624        this.emitDirectionChange();
625    }
626
627    onDisAppear() {
628        Log.info(TAG, 'onDisAppear');
629    }
630
631    private startAnimation(animationEndMatrix: any): void {
632        this.isInAnimation = true;
633        let animationOption: any = {
634            duration: Constants.OVER_SCALE_ANIME_DURATION,
635            curve: Curve.Ease
636        };
637        Log.debug(TAG, `animationEndMatrix: ${animationEndMatrix.matrix4x4}`);
638        this.broadCast.emit(Constants.ANIMATION_EVENT + this.item.uri + this.timeStamp, [animationOption, animationEndMatrix]);
639    }
640
641    /**
642     * At the end of the animation,
643     * refresh the current parameter values according to the end transformation matrix to ensure continuity and
644     * prevent jumping during the next gesture operation
645     *
646     * @param animationEndMatrix Transformation matrix at end
647     */
648    onAnimationEnd(animationEndMatrix: any): void {
649        if (animationEndMatrix) {
650            Log.info(TAG, `onAnimationEnd: ${animationEndMatrix.matrix4x4}`);
651            this.lastScale = animationEndMatrix.matrix4x4[0];
652            this.scale = 1;
653            this.lastOffset = [animationEndMatrix.matrix4x4[Constants.NUMBER_12], animationEndMatrix.matrix4x4[Constants.NUMBER_13]];
654            this.offset = [0, 0];
655            this.evaluateBounds();
656            this.isInAnimation = false;
657            this.emitDirectionChange();
658        }
659    }
660}