UNPKG

@leafer-ui/core

Version:
1,408 lines (1,284 loc) 47.6 kB
import { Leafer, DragBoundsHelper, State, UI, Rect, Box, Text, Group, emptyData } from "@leafer-ui/draw"; export * from "@leafer-ui/draw"; import { registerUI, Creator, isUndefined, DataHelper, canvasSizeAttrs, LayoutEvent, RenderEvent, Event, EventCreator, registerUIEvent, LeafList, PointHelper, BoundsHelper, LeafHelper, isString, isNumber, Debug, Platform, Bounds, ResizeEvent, LeaferEvent, CanvasManager, Leaf, Matrix, tempBounds, ImageManager, LeaferCanvasBase } from "@leafer/core"; function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; let App = class App extends Leafer { get __tag() { return "App"; } get isApp() { return true; } constructor(userConfig, data) { super(userConfig, data); } init(userConfig, parentApp) { super.init(userConfig, parentApp); if (userConfig) { const {ground: ground, tree: tree, sky: sky, editor: editor} = userConfig; if (ground) this.ground = this.addLeafer(ground); if (tree || editor) this.tree = this.addLeafer(tree || { type: userConfig.type || "design" }); if (sky || editor) this.sky = this.addLeafer(sky); if (editor) Creator.editor(editor, this); } } __setApp() { const {canvas: canvas} = this; const {realCanvas: realCanvas, view: view} = this.config; if (realCanvas || view === this.canvas.view || !canvas.parentView) this.realCanvas = true; else canvas.unrealCanvas(); this.leafer = this; this.watcher.disable(); this.layouter.disable(); } __updateLocalBounds() { this.forEach(leafer => leafer.updateLayout()); super.__updateLocalBounds(); } start() { super.start(); this.forEach(leafer => leafer.start()); } stop() { this.forEach(leafer => leafer.stop()); super.stop(); } unlockLayout() { super.unlockLayout(); this.forEach(leafer => leafer.unlockLayout()); } lockLayout() { super.lockLayout(); this.forEach(leafer => leafer.lockLayout()); } forceRender(bounds, sync) { this.forEach(leafer => leafer.forceRender(bounds, sync)); } addLeafer(merge) { const leafer = new Leafer(merge); this.add(leafer); return leafer; } add(leafer, index) { if (!leafer.view) { if (this.realCanvas && !this.canvas.bounds) { setTimeout(() => this.add(leafer, index), 10); return; } leafer.init(this.__getChildConfig(leafer.userConfig), this); } super.add(leafer, index); if (!isUndefined(index)) leafer.canvas.childIndex = index; this.__listenChildEvents(leafer); } forEach(fn) { this.children.forEach(fn); } __onCreated() { this.created = this.children.every(child => child.created); } __onReady() { if (this.children.every(child => child.ready)) super.__onReady(); } __onViewReady() { if (this.children.every(child => child.viewReady)) super.__onViewReady(); } __onChildRenderEnd(e) { this.renderer.addBlock(e.renderBounds); if (this.viewReady) this.renderer.update(); } __render(canvas, options) { if (canvas.context) this.forEach(leafer => options.matrix ? leafer.__render(canvas, options) : canvas.copyWorld(leafer.canvas, options.bounds)); } __onResize(event) { this.forEach(leafer => leafer.resize(event)); super.__onResize(event); } updateLayout() { this.forEach(leafer => leafer.updateLayout()); } __getChildConfig(userConfig) { const config = Object.assign({}, this.config); config.hittable = config.realCanvas = undefined; if (userConfig) DataHelper.assign(config, userConfig); if (this.autoLayout) DataHelper.copyAttrs(config, this, canvasSizeAttrs); config.view = this.realCanvas ? undefined : this.view; config.fill = undefined; return config; } __listenChildEvents(leafer) { leafer.once([ [ LayoutEvent.END, this.__onReady, this ], [ RenderEvent.START, this.__onCreated, this ], [ RenderEvent.END, this.__onViewReady, this ] ]); if (this.realCanvas) this.__eventIds.push(leafer.on_(RenderEvent.END, this.__onChildRenderEnd, this)); } }; App = __decorate([ registerUI() ], App); const downKeyMap = {}; const Keyboard = { isHoldSpaceKey() { return Keyboard.isHold("Space"); }, isHold(code) { return downKeyMap[code]; }, isHoldKeys(shortcutKeys, e) { return e ? shortcutKeys(e) : undefined; }, setDownCode(code) { if (!downKeyMap[code]) downKeyMap[code] = true; }, setUpCode(code) { downKeyMap[code] = false; } }; const PointerButton = { LEFT: 1, RIGHT: 2, MIDDLE: 4, defaultLeft(event) { if (!event.buttons) event.buttons = 1; }, left(event) { return event.buttons === 1; }, right(event) { return event.buttons === 2; }, middle(event) { return event.buttons === 4; } }; class UIEvent extends Event { get spaceKey() { return Keyboard.isHoldSpaceKey(); } get left() { return PointerButton.left(this); } get right() { return PointerButton.right(this); } get middle() { return PointerButton.middle(this); } constructor(params) { super(params.type); this.bubbles = true; Object.assign(this, params); } isHoldKeys(shortcutKeys) { return Keyboard.isHoldKeys(shortcutKeys, this); } getBoxPoint(relative) { return (relative || this.current).getBoxPoint(this); } getInnerPoint(relative) { return (relative || this.current).getInnerPoint(this); } getLocalPoint(relative) { return (relative || this.current).getLocalPoint(this); } getPagePoint() { return this.current.getPagePoint(this); } getInner(relative) { return this.getInnerPoint(relative); } getLocal(relative) { return this.getLocalPoint(relative); } getPage() { return this.getPagePoint(); } static changeName(oldName, newName) { EventCreator.changeName(oldName, newName); } } let PointerEvent = class PointerEvent extends UIEvent {}; PointerEvent.POINTER = "pointer"; PointerEvent.BEFORE_DOWN = "pointer.before_down"; PointerEvent.BEFORE_MOVE = "pointer.before_move"; PointerEvent.BEFORE_UP = "pointer.before_up"; PointerEvent.DOWN = "pointer.down"; PointerEvent.MOVE = "pointer.move"; PointerEvent.UP = "pointer.up"; PointerEvent.OVER = "pointer.over"; PointerEvent.OUT = "pointer.out"; PointerEvent.ENTER = "pointer.enter"; PointerEvent.LEAVE = "pointer.leave"; PointerEvent.TAP = "tap"; PointerEvent.DOUBLE_TAP = "double_tap"; PointerEvent.CLICK = "click"; PointerEvent.DOUBLE_CLICK = "double_click"; PointerEvent.LONG_PRESS = "long_press"; PointerEvent.LONG_TAP = "long_tap"; PointerEvent.MENU = "pointer.menu"; PointerEvent.MENU_TAP = "pointer.menu_tap"; PointerEvent = __decorate([ registerUIEvent() ], PointerEvent); const MyPointerEvent = PointerEvent; const tempMove = {}; let DragEvent = class DragEvent extends PointerEvent { static setList(data) { this.list = data instanceof LeafList ? data : new LeafList(data); } static setData(data) { this.data = data; } static getValidMove(leaf, localStart, worldTotal, checkLimit = true) { const move = leaf.getLocalPoint(worldTotal, null, true); PointHelper.move(move, localStart.x - leaf.x, localStart.y - leaf.y); if (checkLimit) this.limitMove(leaf, move); DragBoundsHelper.axisMove(leaf, move); return move; } static limitMove(leaf, move) { DragBoundsHelper.limitMove(leaf, move); } getPageMove(total) { this.assignMove(total); return this.current.getPagePoint(tempMove, null, true); } getInnerMove(relative, total) { if (!relative) relative = this.current; this.assignMove(total); return relative.getInnerPoint(tempMove, null, true); } getLocalMove(relative, total) { if (!relative) relative = this.current; this.assignMove(total); return relative.getLocalPoint(tempMove, null, true); } getPageTotal() { return this.getPageMove(true); } getInnerTotal(relative) { return this.getInnerMove(relative, true); } getLocalTotal(relative) { return this.getLocalMove(relative, true); } getPageBounds() { const total = this.getPageTotal(), start = this.getPagePoint(), bounds = {}; BoundsHelper.set(bounds, start.x - total.x, start.y - total.y, total.x, total.y); BoundsHelper.unsign(bounds); return bounds; } assignMove(total) { tempMove.x = total ? this.totalX : this.moveX; tempMove.y = total ? this.totalY : this.moveY; } }; DragEvent.BEFORE_DRAG = "drag.before_drag"; DragEvent.START = "drag.start"; DragEvent.DRAG = "drag"; DragEvent.END = "drag.end"; DragEvent.OVER = "drag.over"; DragEvent.OUT = "drag.out"; DragEvent.ENTER = "drag.enter"; DragEvent.LEAVE = "drag.leave"; DragEvent = __decorate([ registerUIEvent() ], DragEvent); const MyDragEvent = DragEvent; let DropEvent = class DropEvent extends PointerEvent { static setList(data) { DragEvent.setList(data); } static setData(data) { DragEvent.setData(data); } }; DropEvent.DROP = "drop"; DropEvent = __decorate([ registerUIEvent() ], DropEvent); let MoveEvent = class MoveEvent extends DragEvent {}; MoveEvent.BEFORE_MOVE = "move.before_move"; MoveEvent.START = "move.start"; MoveEvent.MOVE = "move"; MoveEvent.END = "move.end"; MoveEvent = __decorate([ registerUIEvent() ], MoveEvent); let RotateEvent = class RotateEvent extends PointerEvent {}; RotateEvent.BEFORE_ROTATE = "rotate.before_rotate"; RotateEvent.START = "rotate.start"; RotateEvent.ROTATE = "rotate"; RotateEvent.END = "rotate.end"; RotateEvent = __decorate([ registerUIEvent() ], RotateEvent); let SwipeEvent = class SwipeEvent extends DragEvent {}; SwipeEvent.SWIPE = "swipe"; SwipeEvent.LEFT = "swipe.left"; SwipeEvent.RIGHT = "swipe.right"; SwipeEvent.UP = "swipe.up"; SwipeEvent.DOWN = "swipe.down"; SwipeEvent = __decorate([ registerUIEvent() ], SwipeEvent); let ZoomEvent = class ZoomEvent extends PointerEvent {}; ZoomEvent.BEFORE_ZOOM = "zoom.before_zoom"; ZoomEvent.START = "zoom.start"; ZoomEvent.ZOOM = "zoom"; ZoomEvent.END = "zoom.end"; ZoomEvent = __decorate([ registerUIEvent() ], ZoomEvent); let KeyEvent = class KeyEvent extends UIEvent {}; KeyEvent.BEFORE_DOWN = "key.before_down"; KeyEvent.BEFORE_UP = "key.before_up"; KeyEvent.DOWN = "key.down"; KeyEvent.HOLD = "key.hold"; KeyEvent.UP = "key.up"; KeyEvent = __decorate([ registerUIEvent() ], KeyEvent); const InteractionHelper = { getDragEventData(startPoint, lastPoint, event) { return Object.assign(Object.assign({}, event), { x: event.x, y: event.y, moveX: event.x - lastPoint.x, moveY: event.y - lastPoint.y, totalX: event.x - startPoint.x, totalY: event.y - startPoint.y }); }, getDropEventData(event, list, data) { return Object.assign(Object.assign({}, event), { list: list, data: data }); }, getSwipeDirection(angle) { if (angle < -45 && angle > -135) return SwipeEvent.UP; else if (angle > 45 && angle < 135) return SwipeEvent.DOWN; else if (angle <= 45 && angle >= -45) return SwipeEvent.RIGHT; else return SwipeEvent.LEFT; }, getSwipeEventData(startPoint, lastDragData, event) { return Object.assign(Object.assign({}, event), { moveX: lastDragData.moveX, moveY: lastDragData.moveY, totalX: event.x - startPoint.x, totalY: event.y - startPoint.y, type: I.getSwipeDirection(PointHelper.getAngle(startPoint, event)) }); }, getBase(e) { const pointerUpButtons = e.button === 1 ? 4 : e.button; return { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey, buttons: isUndefined(e.buttons) ? 1 : e.buttons === 0 ? pointerUpButtons : e.buttons, origin: e }; }, pathHasEventType(path, type) { const {list: list} = path; for (let i = 0, len = list.length; i < len; i++) { if (list[i].hasEvent(type)) return true; } return false; }, filterPathByEventType(path, type) { const find = new LeafList; const {list: list} = path; for (let i = 0, len = list.length; i < len; i++) { if (list[i].hasEvent(type)) find.add(list[i]); } return find; }, pathCanDrag(path) { return path && path.list.some(item => LeafHelper.draggable(item) || !item.isLeafer && item.hasEvent(DragEvent.DRAG)); }, pathHasOutside(path) { return path && path.list.some(item => item.isOutside); } }; const I = InteractionHelper; const emptyList = new LeafList; const {getDragEventData: getDragEventData, getDropEventData: getDropEventData, getSwipeEventData: getSwipeEventData} = InteractionHelper; class Dragger { constructor(interaction) { this.interaction = interaction; } setDragData(data) { if (this.animateWait) this.dragEndReal(); this.downData = this.interaction.downData; this.dragData = getDragEventData(data, data, data); this.canAnimate = this.canDragOut = true; } getList(realDraggable, hover) { const {proxy: proxy} = this.interaction.selector; const hasProxyList = proxy && proxy.list.length, dragList = DragEvent.list || this.draggableList || emptyList; return this.dragging && (hasProxyList ? realDraggable ? emptyList : new LeafList(hover ? [ ...proxy.list, ...proxy.dragHoverExclude ] : proxy.list) : dragList); } checkDrag(data, canDrag) { const {interaction: interaction} = this; if (this.moving && data.buttons < 1) { this.canAnimate = false; interaction.pointerCancel(); return; } if (!this.moving && canDrag) { if (this.moving = interaction.canMove(this.downData) || interaction.isHoldRightKey || interaction.isMobileDragEmpty) { this.dragData.moveType = "drag"; interaction.emit(MoveEvent.START, this.dragData); } } if (!this.moving) this.dragStart(data, canDrag); this.drag(data); } dragStart(data, canDrag) { if (!this.dragging) { this.dragging = canDrag && PointerButton.left(data); if (this.dragging) { this.interaction.emit(DragEvent.START, this.dragData); this.getDraggableList(this.dragData.path); this.setDragStartPoints(this.realDraggableList = this.getList(true)); } } } setDragStartPoints(list) { this.dragStartPoints = {}; list.forEach(leaf => this.dragStartPoints[leaf.innerId] = { x: leaf.x, y: leaf.y }); } getDraggableList(path) { let leaf; for (let i = 0, len = path.length; i < len; i++) { leaf = path.list[i]; if (LeafHelper.draggable(leaf)) { this.draggableList = new LeafList(leaf); break; } } } drag(data) { const {interaction: interaction, dragData: dragData, downData: downData} = this; const {path: path, throughPath: throughPath} = downData; this.dragData = getDragEventData(downData, dragData, data); if (throughPath) this.dragData.throughPath = throughPath; this.dragData.path = path; if (this.moving) { this.dragData.moveType = "drag"; interaction.emit(MoveEvent.BEFORE_MOVE, this.dragData); interaction.emit(MoveEvent.MOVE, this.dragData); } else if (this.dragging) { this.dragReal(); interaction.emit(DragEvent.BEFORE_DRAG, this.dragData); interaction.emit(DragEvent.DRAG, this.dragData); } } dragReal(isDragEnd) { const {interaction: interaction} = this, {running: running} = interaction; const list = this.realDraggableList; if (list.length && running) { const {totalX: totalX, totalY: totalY} = this.dragData, {dragLimitAnimate: dragLimitAnimate} = interaction.p; const checkLimitMove = !dragLimitAnimate || !!isDragEnd; list.forEach(leaf => { if (leaf.draggable) { const axisDrag = isString(leaf.draggable); const move = DragEvent.getValidMove(leaf, this.dragStartPoints[leaf.innerId], { x: totalX, y: totalY }, checkLimitMove || axisDrag); if (dragLimitAnimate && !axisDrag && isDragEnd) LeafHelper.animateMove(leaf, move, isNumber(dragLimitAnimate) ? dragLimitAnimate : .3); else leaf.move(move); } }); } } dragOverOrOut(data) { const {interaction: interaction} = this; const {dragOverPath: dragOverPath} = this; const {path: path} = data; this.dragOverPath = path; if (dragOverPath) { if (path.indexAt(0) !== dragOverPath.indexAt(0)) { interaction.emit(DragEvent.OUT, data, dragOverPath); interaction.emit(DragEvent.OVER, data, path); } } else interaction.emit(DragEvent.OVER, data, path); } dragEnterOrLeave(data) { const {interaction: interaction} = this; const {dragEnterPath: dragEnterPath} = this; const {path: path} = data; interaction.emit(DragEvent.LEAVE, data, dragEnterPath, path); interaction.emit(DragEvent.ENTER, data, path, dragEnterPath); this.dragEnterPath = path; } dragEnd(data, speed) { if (!this.dragging && !this.moving) return; if (this.checkDragEndAnimate(data, speed)) return; this.dragEndReal(data); } dragEndReal(data) { const {interaction: interaction, downData: downData, dragData: dragData} = this; if (!data) data = dragData; const {path: path, throughPath: throughPath} = downData; const endDragData = getDragEventData(downData, data, data); if (throughPath) endDragData.throughPath = throughPath; endDragData.path = path; if (this.moving) { this.moving = false; endDragData.moveType = "drag"; interaction.emit(MoveEvent.END, endDragData); } if (this.dragging) { const dropList = this.getList(); this.dragging = false; if (interaction.p.dragLimitAnimate) this.dragReal(true); interaction.emit(DragEvent.END, endDragData); this.swipe(data, downData, dragData, endDragData); this.drop(data, dropList, this.dragEnterPath); } this.autoMoveCancel(); this.dragReset(); this.animate(null, "off"); } swipe(data, downData, dragData, endDragData) { const {interaction: interaction} = this; if (PointHelper.getDistance(downData, data) > interaction.config.pointer.swipeDistance) { const swipeData = getSwipeEventData(downData, dragData, endDragData); this.interaction.emit(swipeData.type, swipeData); } } drop(data, dropList, dragEnterPath) { const dropData = getDropEventData(data, dropList, DragEvent.data); dropData.path = dragEnterPath; this.interaction.emit(DropEvent.DROP, dropData); this.interaction.emit(DragEvent.LEAVE, data, dragEnterPath); } dragReset() { DragEvent.list = DragEvent.data = this.draggableList = this.dragData = this.downData = this.dragOverPath = this.dragEnterPath = null; } checkDragEndAnimate(_data, _speed) { return false; } animate(_func, _off) {} checkDragOut(_data) {} autoMoveOnDragOut(_data) {} autoMoveCancel() {} destroy() { this.dragReset(); } } const debug = Debug.get("emit"); function emit(type, data, path, excludePath) { if (!path && !data.path) return; let leaf; data.type = type; if (path) { data = Object.assign(Object.assign({}, data), { path: path }); } else { path = data.path; } data.target = path.indexAt(0); try { for (let i = path.length - 1; i > -1; i--) { leaf = path.list[i]; if (emitEvent(leaf, type, data, true, excludePath)) return; if (leaf.isApp) emitAppChildren(leaf, type, data, true, excludePath); } for (let i = 0, len = path.length; i < len; i++) { leaf = path.list[i]; if (leaf.isApp) emitAppChildren(leaf, type, data, false, excludePath); if (emitEvent(leaf, type, data, false, excludePath)) return; } } catch (e) { debug.error(e); } } const allowTypes = [ "move", "zoom", "rotate", "key" ]; function emitAppChildren(leaf, type, data, capture, excludePath) { if (allowTypes.some(name => type.startsWith(name)) && leaf.__.hitChildren && !exclude(leaf, excludePath)) { let child; for (let i = 0, len = leaf.children.length; i < len; i++) { child = leaf.children[i]; if (!data.path.has(child) && child.__.hittable) emitEvent(child, type, data, capture, excludePath); } } } function emitEvent(leaf, type, data, capture, excludePath) { if (leaf.destroyed) return false; if (leaf.__.hitSelf && !exclude(leaf, excludePath)) { if (State.updateEventStyle && !capture) State.updateEventStyle(leaf, type); if (leaf.hasEvent(type, capture)) { data.phase = capture ? 1 : leaf === data.target ? 2 : 3; const event = EventCreator.get(type, data); leaf.emitEvent(event, capture); if (event.isStop) return true; } } return false; } function exclude(leaf, excludePath) { return excludePath && excludePath.has(leaf); } const config = { wheel: { zoomSpeed: .5, moveSpeed: .5, rotateSpeed: .5, delta: { x: 80 / 4, y: 8 } }, pointer: { type: "pointer", snap: true, hitRadius: 5, tapTime: 120, longPressTime: 800, transformTime: 500, hover: true, dragHover: true, dragDistance: 2, swipeDistance: 20 }, touch: { preventDefault: "auto" }, multiTouch: {}, move: { autoDistance: 2 }, zoom: {}, cursor: true, keyEvent: true }; const {pathHasEventType: pathHasEventType, pathCanDrag: pathCanDrag, pathHasOutside: pathHasOutside} = InteractionHelper; class InteractionBase { get dragging() { return this.dragger.dragging; } get transforming() { return this.transformer.transforming; } get moveMode() { return this.m.drag === true || this.isHoldSpaceKey || this.isHoldMiddleKey || this.isHoldRightKey && this.dragger.moving || this.isDragEmpty; } get canHover() { return this.p.hover && !this.config.mobile; } get isDragEmpty() { return this.m.dragEmpty && this.isRootPath(this.hoverData) && (!this.downData || this.isRootPath(this.downData)); } get isMobileDragEmpty() { return this.m.dragEmpty && !this.canHover && this.downData && this.isTreePath(this.downData); } get isHoldMiddleKey() { return this.m.holdMiddleKey && this.downData && PointerButton.middle(this.downData); } get isHoldRightKey() { return this.m.holdRightKey && this.downData && PointerButton.right(this.downData); } get isHoldSpaceKey() { return this.m.holdSpaceKey && Keyboard.isHoldSpaceKey(); } get m() { return this.config.move; } get p() { return this.config.pointer; } get hitRadius() { return this.p.hitRadius; } constructor(target, canvas, selector, userConfig) { this.config = DataHelper.clone(config); this.tapCount = 0; this.downKeyMap = {}; this.target = target; this.canvas = canvas; this.selector = selector; this.defaultPath = new LeafList(target); this.createTransformer(); this.dragger = new Dragger(this); if (userConfig) this.config = DataHelper.default(userConfig, this.config); this.__listenEvents(); } start() { this.running = true; } stop() { this.running = false; } receive(_event) {} pointerDown(data, useDefaultPath) { if (!data) data = this.hoverData; if (!data) return; PointerButton.defaultLeft(data); this.updateDownData(data); this.checkPath(data, useDefaultPath); this.downTime = Date.now(); this.emit(PointerEvent.BEFORE_DOWN, data); this.emit(PointerEvent.DOWN, data); if (PointerButton.left(data)) { this.tapWait(); this.longPressWait(data); } this.waitRightTap = PointerButton.right(data); this.dragger.setDragData(data); if (!this.isHoldRightKey) this.updateCursor(data); } pointerMove(data) { if (!data) data = this.hoverData; if (!data) return; const {downData: downData} = this; if (downData) PointerButton.defaultLeft(data); const hit = this.canvas.bounds.hitPoint(data); if (hit || downData) { this.pointerMoveReal(data); if (downData) this.dragger.checkDragOut(data); } } pointerMoveReal(data) { this.emit(PointerEvent.BEFORE_MOVE, data, this.defaultPath); if (this.downData) { const canDrag = PointHelper.getDistance(this.downData, data) > this.p.dragDistance; if (canDrag) { this.pointerWaitCancel(); this.waitRightTap = false; } this.dragger.checkDrag(data, canDrag); } if (!this.dragger.moving) { this.updateHoverData(data); this.checkPath(data); this.emit(PointerEvent.MOVE, data); this.pointerHover(data); if (this.dragging) { this.dragger.dragOverOrOut(data); this.dragger.dragEnterOrLeave(data); } } this.updateCursor(this.downData || data); } pointerUp(data) { const {downData: downData} = this; if (!data) data = downData; if (!downData) return; PointerButton.defaultLeft(data); data.multiTouch = downData.multiTouch; this.findPath(data); const upData = Object.assign(Object.assign({}, data), { path: data.path.clone() }); data.path.addList(downData.path.list); this.checkPath(data); this.downData = null; this.emit(PointerEvent.BEFORE_UP, data); this.emit(PointerEvent.UP, data); this.touchLeave(data); if (!data.isCancel) { this.tap(data); this.menuTap(data); } this.dragger.dragEnd(data); this.updateCursor(upData); } pointerCancel() { const data = Object.assign({}, this.dragger.dragData); data.isCancel = true; this.pointerUp(data); } menu(data) { this.findPath(data); this.emit(PointerEvent.MENU, data); this.waitMenuTap = true; if (!this.downData && this.waitRightTap) this.menuTap(data); } menuTap(data) { if (this.waitRightTap && this.waitMenuTap) { this.emit(PointerEvent.MENU_TAP, data); this.waitRightTap = this.waitMenuTap = false; } } createTransformer() {} move(_data) {} zoom(_data) {} rotate(_data) {} transformEnd() {} wheel(_data) {} multiTouch(_data, _list) {} keyDown(data) { if (!this.config.keyEvent) return; this.emit(KeyEvent.BEFORE_DOWN, data, this.defaultPath); const {code: code} = data; if (!this.downKeyMap[code]) { this.downKeyMap[code] = true; Keyboard.setDownCode(code); this.emit(KeyEvent.HOLD, data, this.defaultPath); if (this.moveMode) { this.cancelHover(); this.updateCursor(); } } this.emit(KeyEvent.DOWN, data, this.defaultPath); } keyUp(data) { if (!this.config.keyEvent) return; this.emit(KeyEvent.BEFORE_UP, data, this.defaultPath); const {code: code} = data; this.downKeyMap[code] = false; Keyboard.setUpCode(code); this.emit(KeyEvent.UP, data, this.defaultPath); if (this.cursor === "grab") this.updateCursor(); } pointerHover(data) { if (this.canHover && !(this.dragging && !this.p.dragHover)) { data.path || (data.path = new LeafList); this.pointerOverOrOut(data); this.pointerEnterOrLeave(data); } } pointerOverOrOut(data) { const {path: path} = data; const {overPath: overPath} = this; this.overPath = path; if (overPath) { if (path.indexAt(0) !== overPath.indexAt(0)) { this.emit(PointerEvent.OUT, data, overPath); this.emit(PointerEvent.OVER, data, path); } } else { this.emit(PointerEvent.OVER, data, path); } } pointerEnterOrLeave(data) { let {path: path} = data; if (this.downData && !this.moveMode) { path = path.clone(); this.downData.path.forEach(leaf => path.add(leaf)); } const {enterPath: enterPath} = this; this.enterPath = path; this.emit(PointerEvent.LEAVE, data, enterPath, path); this.emit(PointerEvent.ENTER, data, path, enterPath); } touchLeave(data) { if (data.pointerType === "touch") { if (this.enterPath) { this.emit(PointerEvent.LEAVE, data); if (this.dragger.dragging) this.emit(DropEvent.LEAVE, data); } } } tap(data) { const {pointer: pointer} = this.config; const hasLong = this.longTap(data); if (!pointer.tapMore && hasLong) return; if (!this.waitTap) return; if (pointer.tapMore) this.emitTap(data); const useTime = Date.now() - this.downTime; const hasDouble = [ PointerEvent.DOUBLE_TAP, PointerEvent.DOUBLE_CLICK ].some(type => pathHasEventType(data.path, type)); if (useTime < pointer.tapTime + 50 && hasDouble) { this.tapCount++; if (this.tapCount === 2) { this.tapWaitCancel(); this.emitDoubleTap(data); } else { clearTimeout(this.tapTimer); this.tapTimer = setTimeout(() => { if (!pointer.tapMore) { this.tapWaitCancel(); this.emitTap(data); } }, pointer.tapTime); } } else { if (!pointer.tapMore) { this.tapWaitCancel(); this.emitTap(data); } } } findPath(data, options) { const {hitRadius: hitRadius, through: through} = this.p; const {bottomList: bottomList, target: target} = this; if (!Platform.backgrounder && !data.origin) target && target.updateLayout(); const find = this.selector.getByPoint(data, hitRadius, Object.assign({ bottomList: bottomList, name: data.type }, options || { through: through })); if (find.throughPath) data.throughPath = find.throughPath; data.path = find.path; return find.path; } isRootPath(data) { return data && data.path.list[0].isLeafer; } isTreePath(data) { const app = this.target.app; if (!app || !app.isApp) return false; return app.editor && (!data.path.has(app.editor) && data.path.has(app.tree) && !data.target.syncEventer); } checkPath(data, useDefaultPath) { if (useDefaultPath || this.moveMode && !pathHasOutside(data.path)) data.path = this.defaultPath; } canMove(data) { return data && (this.moveMode || this.m.drag === "auto" && !pathCanDrag(data.path)) && !pathHasOutside(data.path); } isDrag(leaf) { return this.dragger.getList().has(leaf); } isPress(leaf) { return this.downData && this.downData.path.has(leaf); } isHover(leaf) { return this.enterPath && this.enterPath.has(leaf); } isFocus(leaf) { return this.focusData === leaf; } cancelHover() { const {hoverData: hoverData} = this; if (hoverData) { hoverData.path = this.defaultPath; this.pointerHover(hoverData); } } updateDownData(data, options, merge) { const {downData: downData} = this; if (!data && downData) data = downData; if (!data) return; this.findPath(data, options); if (merge && downData) data.path.addList(downData.path.list); this.downData = data; } updateHoverData(data) { if (!data) data = this.hoverData; if (!data) return; this.findPath(data, { exclude: this.dragger.getList(false, true), name: PointerEvent.MOVE }); this.hoverData = data; } updateCursor(data) { if (!this.config.cursor || !this.canHover) return; if (!data) { this.updateHoverData(); data = this.downData || this.hoverData; } if (this.dragger.moving) { return this.setCursor("grabbing"); } else if (this.canMove(data)) { return this.setCursor(this.downData ? "grabbing" : "grab"); } else if (!data) return; let leaf, cursor; const {path: path} = data; for (let i = 0, len = path.length; i < len; i++) { leaf = path.list[i]; cursor = leaf.syncEventer && leaf.syncEventer.cursor || leaf.cursor; if (cursor) break; } this.setCursor(cursor); } setCursor(cursor) { this.cursor = cursor; } getLocal(clientPoint, updateClient) { const clientBounds = this.canvas.getClientBounds(updateClient); const point = { x: clientPoint.clientX - clientBounds.x, y: clientPoint.clientY - clientBounds.y }; const {bounds: bounds} = this.canvas; point.x *= bounds.width / clientBounds.width; point.y *= bounds.height / clientBounds.height; if (this.p.snap) PointHelper.round(point); return point; } emitTap(data) { this.emit(PointerEvent.TAP, data); this.emit(PointerEvent.CLICK, data); } emitDoubleTap(data) { this.emit(PointerEvent.DOUBLE_TAP, data); this.emit(PointerEvent.DOUBLE_CLICK, data); } pointerWaitCancel() { this.tapWaitCancel(); this.longPressWaitCancel(); } tapWait() { clearTimeout(this.tapTimer); this.waitTap = true; } tapWaitCancel() { if (this.waitTap) { clearTimeout(this.tapTimer); this.waitTap = false; this.tapCount = 0; } } longPressWait(data) { clearTimeout(this.longPressTimer); this.longPressTimer = setTimeout(() => { this.longPressed = true; this.emit(PointerEvent.LONG_PRESS, data); }, this.p.longPressTime); } longTap(data) { let hasLong; if (this.longPressed) { this.emit(PointerEvent.LONG_TAP, data); if (pathHasEventType(data.path, PointerEvent.LONG_TAP) || pathHasEventType(data.path, PointerEvent.LONG_PRESS)) hasLong = true; } this.longPressWaitCancel(); return hasLong; } longPressWaitCancel() { if (this.longPressTimer) { clearTimeout(this.longPressTimer); this.longPressed = false; } } __onResize() { const {dragOut: dragOut} = this.m; this.shrinkCanvasBounds = new Bounds(this.canvas.bounds); this.shrinkCanvasBounds.spread(-(isNumber(dragOut) ? dragOut : 2)); } __listenEvents() { const {target: target} = this; this.__eventIds = [ target.on_(ResizeEvent.RESIZE, this.__onResize, this) ]; target.once(LeaferEvent.READY, () => this.__onResize()); } __removeListenEvents() { this.target.off_(this.__eventIds); this.__eventIds.length = 0; } emit(type, data, path, excludePath) { if (this.running) emit(type, data, path, excludePath); } destroy() { if (this.__eventIds.length) { this.stop(); this.__removeListenEvents(); this.dragger.destroy(); if (this.transformer) this.transformer.destroy(); this.downData = this.overPath = this.enterPath = null; } } } class Cursor { static set(name, value) { this.custom[name] = value; } static get(name) { return this.custom[name]; } } Cursor.custom = {}; class HitCanvasManager extends CanvasManager { constructor() { super(...arguments); this.maxTotal = 1e3; this.pathList = new LeafList; this.pixelList = new LeafList; } getPixelType(leaf, config) { this.__autoClear(); this.pixelList.add(leaf); return Creator.hitCanvas(config); } getPathType(leaf) { this.__autoClear(); this.pathList.add(leaf); return Creator.hitCanvas(); } clearImageType() { this.__clearLeafList(this.pixelList); } clearPathType() { this.__clearLeafList(this.pathList); } __clearLeafList(leafList) { if (leafList.length) { leafList.forEach(leaf => { if (leaf.__hitCanvas) { leaf.__hitCanvas.destroy(); leaf.__hitCanvas = null; } }); leafList.reset(); } } __autoClear() { if (this.pathList.length + this.pixelList.length > this.maxTotal) this.clear(); } clear() { this.clearPathType(); this.clearImageType(); } } Platform.getSelector = function(leaf) { return leaf.leafer ? leaf.leafer.selector : Platform.selector || (Platform.selector = Creator.selector()); }; const {toInnerRadiusPointOf: toInnerRadiusPointOf, copy: copy, setRadius: setRadius} = PointHelper; const {hitRadiusPoint: hitRadiusPoint, hitPoint: hitPoint} = BoundsHelper; const inner = {}, worldRadiusPoint = {}; const leaf = Leaf.prototype; leaf.hit = function(worldPoint, hitRadius = 0) { this.updateLayout(); copy(worldRadiusPoint, worldPoint); setRadius(worldRadiusPoint, hitRadius); const world = this.__world; if (hitRadius ? !hitRadiusPoint(world, worldRadiusPoint) : !hitPoint(world, worldRadiusPoint)) return false; return this.isBranch ? Platform.getSelector(this).hitPoint(Object.assign({}, worldRadiusPoint), hitRadius, { target: this }) : this.__hitWorld(worldRadiusPoint); }; leaf.__hitWorld = function(point) { const data = this.__; if (!data.hitSelf) return false; const world = this.__world, layout = this.__layout; const isSmall = world.width < 10 && world.height < 10; if (data.hitRadius) { copy(inner, point), point = inner; setRadius(point, data.hitRadius); } toInnerRadiusPointOf(point, world, inner); if (data.hitBox || isSmall) { if (BoundsHelper.hitRadiusPoint(layout.boxBounds, inner)) return true; if (isSmall) return false; } if (layout.hitCanvasChanged || !this.__hitCanvas) { this.__updateHitCanvas(); if (!layout.boundsChanged) layout.hitCanvasChanged = false; } return this.__hit(inner); }; leaf.__hitFill = function(inner) { const h = this.__hitCanvas; return h && h.hitFill(inner, this.__.windingRule); }; leaf.__hitStroke = function(inner, strokeWidth) { const h = this.__hitCanvas; return h && h.hitStroke(inner, strokeWidth); }; leaf.__hitPixel = function(inner) { const h = this.__hitCanvas; return h && h.hitPixel(inner, this.__layout.renderBounds, h.hitScale); }; leaf.__drawHitPath = function(canvas) { canvas && this.__drawRenderPath(canvas); }; const matrix = new Matrix; const ui$1 = UI.prototype; ui$1.__updateHitCanvas = function() { if (this.__box) this.__box.__updateHitCanvas(); const leafer = this.leafer || this.parent && this.parent.leafer; if (!leafer) return; const data = this.__, {hitCanvasManager: hitCanvasManager} = leafer; const isHitPixelFill = (data.__isAlphaPixelFill || data.__isCanvas) && data.hitFill === "pixel"; const isHitPixelStroke = data.__isAlphaPixelStroke && data.hitStroke === "pixel"; const isHitPixel = isHitPixelFill || isHitPixelStroke; if (!this.__hitCanvas) this.__hitCanvas = isHitPixel ? hitCanvasManager.getPixelType(this, { contextSettings: { willReadFrequently: true } }) : hitCanvasManager.getPathType(this); const h = this.__hitCanvas; if (isHitPixel) { const {renderBounds: renderBounds} = this.__layout; const size = Platform.image.hitCanvasSize; const scale = h.hitScale = tempBounds.set(0, 0, size, size).getFitMatrix(renderBounds).a; const {x: x, y: y, width: width, height: height} = tempBounds.set(renderBounds).scale(scale); h.resize({ width: width, height: height, pixelRatio: 1 }); h.clear(); ImageManager.patternLocked = true; this.__renderShape(h, { matrix: matrix.setWith(this.__world).scaleWith(1 / scale).invertWith().translate(-x, -y), ignoreFill: !isHitPixelFill, ignoreStroke: !isHitPixelStroke }); ImageManager.patternLocked = false; h.resetTransform(); data.__isHitPixel = true; } else { data.__isHitPixel && (data.__isHitPixel = false); } this.__drawHitPath(h); h.setStrokeOptions(data); }; ui$1.__hit = function(inner) { if (this.__box && this.__box.__hit(inner)) return true; const data = this.__; if (data.__isHitPixel && this.__hitPixel(inner)) return true; const {hitFill: hitFill} = data; const needHitFillPath = (data.fill || data.__isCanvas) && (hitFill === "path" || hitFill === "pixel" && !(data.__isAlphaPixelFill || data.__isCanvas)) || hitFill === "all"; if (needHitFillPath && this.__hitFill(inner)) return true; const {hitStroke: hitStroke, __maxStrokeWidth: strokeWidth} = data; const needHitStrokePath = data.stroke && (hitStroke === "path" || hitStroke === "pixel" && !data.__isAlphaPixelStroke) || hitStroke === "all"; if (!needHitFillPath && !needHitStrokePath) return false; const radiusWidth = inner.radiusX * 2; let hitWidth = radiusWidth; if (needHitStrokePath) { switch (data.strokeAlign) { case "inside": hitWidth += strokeWidth * 2; if (!needHitFillPath && this.__hitFill(inner) && this.__hitStroke(inner, hitWidth)) return true; hitWidth = radiusWidth; break; case "center": hitWidth += strokeWidth; break; case "outside": hitWidth += strokeWidth * 2; if (!needHitFillPath) { if (!this.__hitFill(inner) && this.__hitStroke(inner, hitWidth)) return true; hitWidth = radiusWidth; } break; } } return hitWidth ? this.__hitStroke(inner, hitWidth) : false; }; const ui = UI.prototype, rect = Rect.prototype, box = Box.prototype; rect.__updateHitCanvas = box.__updateHitCanvas = function() { if (this.stroke || this.cornerRadius || (this.fill || this.__.__isCanvas) && this.hitFill === "pixel" || this.hitStroke === "all") ui.__updateHitCanvas.call(this); else if (this.__hitCanvas) this.__hitCanvas = null; }; rect.__hitFill = box.__hitFill = function(inner) { return this.__hitCanvas ? ui.__hitFill.call(this, inner) : BoundsHelper.hitRadiusPoint(this.__layout.boxBounds, inner); }; Text.prototype.__drawHitPath = function(canvas) { const {__lineHeight: __lineHeight, fontSize: fontSize, __baseLine: __baseLine, __letterSpacing: __letterSpacing, __textDrawData: data} = this.__; canvas.beginPath(); if (__letterSpacing < 0) this.__drawPathByBox(canvas); else data.rows.forEach(row => canvas.rect(row.x, row.y - __baseLine, row.width, __lineHeight < fontSize ? fontSize : __lineHeight)); }; Group.prototype.pick = function(hitPoint, options) { options || (options = emptyData); this.updateLayout(); return Platform.getSelector(this).getByPoint(hitPoint, options.hitRadius || 0, Object.assign(Object.assign({}, options), { target: this })); }; const canvas = LeaferCanvasBase.prototype; canvas.hitFill = function(point, fillRule) { return fillRule ? this.context.isPointInPath(point.x, point.y, fillRule) : this.context.isPointInPath(point.x, point.y); }; canvas.hitStroke = function(point, strokeWidth) { this.strokeWidth = strokeWidth; return this.context.isPointInStroke(point.x, point.y); }; canvas.hitPixel = function(radiusPoint, offset, scale = 1) { let {x: x, y: y, radiusX: radiusX, radiusY: radiusY} = radiusPoint; if (offset) x -= offset.x, y -= offset.y; tempBounds.set(x - radiusX, y - radiusY, radiusX * 2, radiusY * 2).scale(scale).ceil(); const {data: data} = this.context.getImageData(tempBounds.x, tempBounds.y, tempBounds.width || 1, tempBounds.height || 1); for (let i = 0, len = data.length; i < len; i += 4) { if (data[i + 3] > 0) return true; } return data[3] > 0; }; export { App, Cursor, DragEvent, Dragger, DropEvent, HitCanvasManager, InteractionBase, InteractionHelper, KeyEvent, Keyboard, MoveEvent, MyDragEvent, MyPointerEvent, PointerButton, PointerEvent, RotateEvent, SwipeEvent, UIEvent, ZoomEvent };