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}