/* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ import { ComponentEvent } from "@egjs/component"; import { getObserver, Observe } from "@cfcs/core"; import { InputType } from "./inputType/InputType"; import { Axis } from "./AxisManager"; import Axes from "./Axes"; import { roundNumbers } from "./utils"; import { AnimationParam, OnAnimationStart, OnRelease } from "./types"; import { AnimationManager } from "./animation/AnimationManager"; export interface ChangeEventOption { input: InputType; event; } export class EventManager { @Observe holdingCount = 0; public animationManager: AnimationManager; public constructor(private _axes: Axes) {} /** * This event is fired when a user holds an element on the screen of the device. * @ko 사용자가 기기의 화면에 손을 대고 있을 때 발생하는 이벤트 * @event Axes#hold * @type {object} * @property {Object.} pos coordinate 좌표 정보 * @property {Object} input The instance of inputType where the event occurred이벤트가 발생한 inputType 인스턴스 * @property {Object} inputEvent The event object received from inputType inputType으로 부터 받은 이벤트 객체 * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("hold", function(event) { * // event.pos * // event.input * // event.inputEvent * // isTrusted * }); * ``` */ public hold(pos: Axis, option: ChangeEventOption) { const { roundPos } = this._getRoundPos(pos); this._axes.trigger( new ComponentEvent("hold", { pos: roundPos, input: option.input || null, inputEvent: option.event || null, isTrusted: true, }) ); } /** * Specifies the coordinates to move after the 'change' event. It works when the holding value of the change event is true. * @ko 'change' 이벤트 이후 이동할 좌표를 지정한다. change이벤트의 holding 값이 true일 경우에 동작한다 * @param {Object.} pos The coordinate to move to 이동할 좌표 * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("change", function(event) { * event.holding && event.set({x: 10}); * }); * ``` */ /** Specifies the animation coordinates to move after the 'release' or 'animationStart' events. * @ko 'release' 또는 'animationStart' 이벤트 이후 이동할 좌표를 지정한다. * @param {Object.} pos The coordinate to move to 이동할 좌표 * @param {Number} [duration=0] Duration of the animation (unit: ms) 애니메이션 진행 시간(단위: ms) * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("animationStart", function(event) { * event.setTo({x: 10}, 2000); * }); * ``` */ /** * This event is fired when a user release an element on the screen of the device. * @ko 사용자가 기기의 화면에서 손을 뗐을 때 발생하는 이벤트 * @event Axes#release * @type {object} * @property {Object.} depaPos The coordinates when releasing an element손을 뗐을 때의 좌표 * @property {Object.} destPos The coordinates to move to after releasing an element손을 뗀 뒤에 이동할 좌표 * @property {Object.} delta The movement variation of coordinate 좌표의 변화량 * @property {Object.} bounceRatio If the coordinates at the time of release are in the bounce area, the current bounce value divided by the maximum bounce value 손을 뗐을 때의 좌표가 bounce 영역에 있는 경우 현재 bounce된 값을 최대 bounce 값으로 나눈 수치. * @property {Object} inputEvent The event object received from inputType inputType으로 부터 받은 이벤트 객체 * @property {Object} input The instance of inputType where the event occurred이벤트가 발생한 inputType 인스턴스 * @property {setTo} setTo Specifies the animation coordinates to move after the event 이벤트 이후 이동할 애니메이션 좌표를 지정한다 * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("release", function(event) { * // event.depaPos * // event.destPos * // event.delta * // event.input * // event.inputEvent * // event.setTo * // event.isTrusted * * // if you want to change the animation coordinates to move after the 'release' event. * event.setTo({x: 10}, 2000); * }); * ``` */ public triggerRelease(param: AnimationParam) { const { roundPos, roundDepa } = this._getRoundPos( param.destPos, param.depaPos ); param.destPos = roundPos; param.depaPos = roundDepa; param.setTo = this._createUserControll(param.destPos, param.duration); this._axes.trigger( new ComponentEvent("release", { ...param, bounceRatio: this._getBounceRatio(roundPos), } as OnRelease) ); } /** * This event is fired when coordinate changes. * @ko 좌표가 변경됐을 때 발생하는 이벤트 * @event Axes#change * @type {object} * @property {Object.} pos The coordinate 좌표 * @property {Object.} delta The movement variation of coordinate 좌표의 변화량 * @property {Object.} bounceRatio If the current coordinates are in the bounce area, the current bounce value divided by the maximum bounce value 현재 좌표가 bounce 영역에 있는 경우 현재 bounce된 값을 최대 bounce 값으로 나눈 수치. * @property {Boolean} holding Indicates whether a user holds an element on the screen of the device.사용자가 기기의 화면을 누르고 있는지 여부 * @property {Object} input The instance of inputType where the event occurred. If the value is changed by animation, it returns 'null'.이벤트가 발생한 inputType 인스턴스. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다. * @property {Object} inputEvent The event object received from inputType. If the value is changed by animation, it returns 'null'.inputType으로 부터 받은 이벤트 객체. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다. * @property {set} set Specifies the coordinates to move after the event. It works when the holding value is true 이벤트 이후 이동할 좌표를 지정한다. holding 값이 true일 경우에 동작한다. * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("change", function(event) { * // event.pos * // event.delta * // event.input * // event.inputEvent * // event.holding * // event.set * // event.isTrusted * * // if you want to change the coordinates to move after the 'change' event. * // it works when the holding value of the change event is true. * event.holding && event.set({x: 10}); * }); * ``` */ public triggerChange( pos: Axis, depaPos?: Axis, option?: ChangeEventOption, holding: boolean = false ) { const animationManager = this.animationManager; const axisManager = animationManager.axisManager; const eventInfo = animationManager.getEventInfo(); const { roundPos, roundDepa } = this._getRoundPos(pos, depaPos); const moveTo = axisManager.moveTo(roundPos, roundDepa); const inputEvent = option?.event || eventInfo?.event || null; const param = { pos: moveTo.pos, delta: moveTo.delta, bounceRatio: this._getBounceRatio(moveTo.pos), holding, inputEvent, isTrusted: !!inputEvent, input: option?.input || eventInfo?.input || null, set: inputEvent ? this._createUserControll(moveTo.pos) : () => {}, // eslint-disable-line @typescript-eslint/no-empty-function }; const event = new ComponentEvent("change", param); this._axes.trigger(event); Object.keys(moveTo.pos).forEach((axis) => { const p = moveTo.pos[axis]; getObserver(this._axes, axis, p).current = p; }); if (inputEvent) { axisManager.set( (param.set() as { destPos: Axis; duration: number }).destPos ); } return !event.isCanceled(); } /** * This event is fired when animation starts. * @ko 에니메이션이 시작할 때 발생한다. * @event Axes#animationStart * @type {object} * @property {Object.} depaPos The coordinates when animation starts애니메이션이 시작 되었을 때의 좌표 * @property {Object.} destPos The coordinates to move to. If you change this value, you can run the animation이동할 좌표. 이값을 변경하여 애니메이션을 동작시킬수 있다 * @property {Object.} delta The movement variation of coordinate 좌표의 변화량 * @property {Number} duration Duration of the animation (unit: ms). If you change this value, you can control the animation duration time.애니메이션 진행 시간(단위: ms). 이값을 변경하여 애니메이션의 이동시간을 조절할 수 있다. * @property {Object} input The instance of inputType where the event occurred. If the value is changed by animation, it returns 'null'.이벤트가 발생한 inputType 인스턴스. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다. * @property {Object} inputEvent The event object received from inputType inputType으로 부터 받은 이벤트 객체 * @property {setTo} setTo Specifies the animation coordinates to move after the event 이벤트 이후 이동할 애니메이션 좌표를 지정한다 * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("release", function(event) { * // event.depaPos * // event.destPos * // event.delta * // event.input * // event.inputEvent * // event.setTo * // event.isTrusted * * // if you want to change the animation coordinates to move after the 'animationStart' event. * event.setTo({x: 10}, 2000); * }); * ``` */ public triggerAnimationStart(param: AnimationParam): boolean { const { roundPos, roundDepa } = this._getRoundPos( param.destPos, param.depaPos ); param.destPos = roundPos; param.depaPos = roundDepa; param.setTo = this._createUserControll(param.destPos, param.duration); const event = new ComponentEvent( "animationStart", param as OnAnimationStart ); this._axes.trigger(event); return !event.isCanceled(); } /** * This event is fired when animation ends. * @ko 에니메이션이 끝났을 때 발생한다. * @event Axes#animationEnd * @type {object} * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("animationEnd", function(event) { * // event.isTrusted * }); * ``` */ public triggerAnimationEnd(isTrusted: boolean = false) { this._axes.trigger( new ComponentEvent("animationEnd", { isTrusted, }) ); } /** * This event is fired when all actions have been completed. * @ko 에니메이션이 끝났을 때 발생한다. * @event Axes#finish * @type {object} * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call 사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다. * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("finish", function(event) { * // event.isTrusted * }); * ``` */ public triggerFinish(isTrusted: boolean = false) { this._axes.trigger( new ComponentEvent("finish", { isTrusted, }) ); } public setAnimationManager(animationManager: AnimationManager) { this.animationManager = animationManager; } public destroy() { this._axes.off(); } private _createUserControll(pos: Axis, duration: number = 0) { // to controll const userControl = { destPos: { ...pos }, duration, }; return ( toPos?: Axis, userDuration?: number ): { destPos: Axis; duration: number } => { if (toPos) { userControl.destPos = { ...toPos }; } if (userDuration !== undefined) { userControl.duration = userDuration; } return userControl; }; } private _getRoundPos(pos: Axis, depaPos?: Axis) { // round value if round exist const roundUnit = this._axes.options.round; // if (round == null) { // return {pos, depaPos}; // undefined, undefined // } return { roundPos: roundNumbers(pos, roundUnit), roundDepa: roundNumbers(depaPos, roundUnit), }; } private _getBounceRatio(pos: Axis): Axis { return this._axes.axisManager.map(pos, (v, opt) => { if (v < opt.range[0] && opt.bounce[0] !== 0) { return (opt.range[0] - v) / opt.bounce[0]; } else if (v > opt.range[1] && opt.bounce[1] !== 0) { return (v - opt.range[1]) / opt.bounce[1]; } else { return 0; } }); } }