@leafer-ui/core
Version:
1,346 lines (1,313 loc) • 47.8 kB
JavaScript
import { Leafer, State, UI, Rect, Box, Text, Group, emptyData } from '@leafer-ui/draw';
export * from '@leafer-ui/draw';
import { registerUI, Creator, DataHelper, canvasSizeAttrs, LayoutEvent, RenderEvent, Event, EventCreator, registerUIEvent, LeafList, PointHelper, BoundsHelper, Debug, Platform, Bounds, ResizeEvent, LeaferEvent, CanvasManager, Leaf, Matrix, tempBounds, ImageManager, LeaferCanvasBase } from '@leafer/core';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
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, tree, sky, 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 } = this;
const { realCanvas, 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 (index !== undefined)
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 && 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];
},
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);
}
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, start, total) {
const { draggable, dragBounds } = leaf, move = leaf.getLocalPoint(total, null, true);
PointHelper.move(move, start.x - leaf.x, start.y - leaf.y);
if (dragBounds)
this.getMoveInDragBounds(leaf.__localBoxBounds, dragBounds === 'parent' ? leaf.parent.boxBounds : dragBounds, move, true);
if (draggable === 'x')
move.y = 0;
if (draggable === 'y')
move.x = 0;
return move;
}
static getMoveInDragBounds(childBox, dragBounds, move, change) {
const x = childBox.x + move.x, y = childBox.y + move.y, right = x + childBox.width, bottom = y + childBox.height;
const boundsRight = dragBounds.x + dragBounds.width, boundsBottom = dragBounds.y + dragBounds.height;
if (!change)
move = Object.assign({}, move);
if (BoundsHelper.includes(childBox, dragBounds)) {
if (x > dragBounds.x)
move.x += dragBounds.x - x;
else if (right < boundsRight)
move.x += boundsRight - right;
if (y > dragBounds.y)
move.y += dragBounds.y - y;
else if (bottom < boundsBottom)
move.y += boundsBottom - bottom;
}
else {
if (x < dragBounds.x)
move.x += dragBounds.x - x;
else if (right > boundsRight)
move.x += boundsRight - right;
if (y < dragBounds.y)
move.y += dragBounds.y - y;
else if (bottom > boundsBottom)
move.y += boundsBottom - bottom;
}
return 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.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,
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: e.buttons === undefined ? 1 : (e.buttons === 0 ? pointerUpButtons : e.buttons),
origin: e
};
},
pathHasEventType(path, type) {
const { 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 } = 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 => item.draggable || item.editable || (!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, getDropEventData, 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 } = 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 } = 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 ((leaf.draggable || leaf.editable) && leaf.hitSelf && !leaf.locked) {
this.draggableList = new LeafList(leaf);
break;
}
}
}
drag(data) {
const { interaction, dragData, downData } = this;
const { path, 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() {
const { running } = this.interaction;
const list = this.realDraggableList;
if (list.length && running) {
const { totalX, totalY } = this.dragData;
list.forEach(leaf => leaf.draggable && leaf.move(DragEvent.getValidMove(leaf, this.dragStartPoints[leaf.innerId], { x: totalX, y: totalY })));
}
}
dragOverOrOut(data) {
const { interaction } = this;
const { dragOverPath } = this;
const { 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 } = this;
const { dragEnterPath } = this;
const { 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, downData, dragData } = this;
if (!data)
data = dragData;
const { path, 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;
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 } = 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 });
}
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: 0.5,
moveSpeed: 0.5,
rotateSpeed: 0.5,
delta: { x: 80 / 4, y: 8.0 },
},
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, pathCanDrag, 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 } = 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 } = 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;
const { 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;
const { 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 } = data;
const { 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 } = data;
if (this.downData && !this.moveMode) {
path = path.clone();
this.downData.path.forEach(leaf => path.add(leaf));
}
const { 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 } = 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, through } = this.p;
const { bottomList, target } = this;
if (!Platform.backgrounder && !data.origin)
target && target.updateLayout();
const find = this.selector.getByPoint(data, hitRadius, Object.assign({ bottomList, name: data.type }, (options || { 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 } = this;
if (hoverData) {
hoverData.path = this.defaultPath;
this.pointerHover(hoverData);
}
}
updateDownData(data, options, merge) {
const { 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 } = 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 };
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 } = this.m;
this.shrinkCanvasBounds = new Bounds(this.canvas.bounds);
this.shrinkCanvasBounds.spread(-(typeof dragOut === 'number' ? dragOut : 2));
}
__listenEvents() {
const { 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 = 1000;
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();
}
}
const { toInnerRadiusPointOf, copy, setRadius } = PointHelper;
const inner = {};
const leaf = Leaf.prototype;
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 } = 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 } = this.__layout;
const size = Platform.image.hitCanvasSize;
const scale = h.hitScale = tempBounds.set(0, 0, size, size).getFitMatrix(renderBounds).a;
const { x, y, width, height } = tempBounds.set(renderBounds).scale(scale);
h.resize({ width, 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 } = 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, __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, fontSize, __baseLine, __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));
};
function getSelector(ui) {
return ui.leafer ? ui.leafer.selector : (Platform.selector || (Platform.selector = Creator.selector()));
}
Group.prototype.pick = function (hitPoint, options) {
options || (options = emptyData);
this.updateLayout();
return 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, y, radiusX, 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 } = 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 };