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