mirror of
https://github.com/idrawjs/idraw
synced 2026-05-23 09:38:22 +00:00
feat: improve control of middleware selector
This commit is contained in:
parent
4ecd179940
commit
1dda8be77e
18 changed files with 446 additions and 116 deletions
|
|
@ -122,6 +122,8 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
// }, throttleTime)
|
||||
// );
|
||||
this.#watcher.on('pointMove', this.#handlePointMove.bind(this));
|
||||
this.#watcher.on('pointLeave', this.#handlePointLeave.bind(this));
|
||||
|
||||
this.#watcher.on('hover', this.#handleHover.bind(this));
|
||||
this.#watcher.on('wheel', this.#handleWheel.bind(this));
|
||||
this.#watcher.on('wheelScale', this.#handleWheelScale.bind(this));
|
||||
|
|
@ -166,6 +168,16 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
}
|
||||
}
|
||||
|
||||
#handlePointLeave(e: BoardWatcherEventMap['pointLeave']) {
|
||||
for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) {
|
||||
const obj = this.#activeMiddlewareObjs[i];
|
||||
const result = obj?.pointLeave?.(e);
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#handleHover(e: BoardWatcherEventMap['hover']) {
|
||||
for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) {
|
||||
const obj = this.#activeMiddlewareObjs[i];
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
|
|||
#hasDestroyed: boolean = false;
|
||||
constructor(opts: BoardWatcherOptions) {
|
||||
super();
|
||||
const store = new Store<BoardWatcherStore>({ defaultStorage: { hasPointDown: false, prevClickPoint: null } });
|
||||
const store = new Store<BoardWatcherStore>({ defaultStorage: { hasPointDown: false, prevClickPoint: null, inCanvas: true } });
|
||||
this.#store = store;
|
||||
this.#opts = opts;
|
||||
this.#init();
|
||||
|
|
@ -30,7 +30,7 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
|
|||
container.addEventListener('mousedown', this.#onPointStart);
|
||||
container.addEventListener('mousemove', this.#onPointMove);
|
||||
container.addEventListener('mouseup', this.#onPointEnd);
|
||||
container.addEventListener('mouseleave', this.#onPointLeave);
|
||||
// container.addEventListener('mouseleave', this.#onPointLeave);
|
||||
container.addEventListener('wheel', this.#onWheel, { passive: false });
|
||||
container.addEventListener('click', this.#onClick);
|
||||
container.addEventListener('contextmenu', this.#onContextMenu);
|
||||
|
|
@ -110,9 +110,6 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
|
|||
|
||||
#onPointLeave = (e: MouseEvent) => {
|
||||
this.#store.set('hasPointDown', false);
|
||||
if (!this.#isInTarget(e)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
const point = this.#getPoint(e);
|
||||
this.trigger('pointLeave', { point });
|
||||
|
|
@ -169,8 +166,13 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
|
|||
|
||||
#onHover = (e: MouseEvent) => {
|
||||
if (!this.#isInTarget(e)) {
|
||||
if (this.#store.get('inCanvas') === true) {
|
||||
this.#store.set('inCanvas', false);
|
||||
this.#onPointLeave(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.#store.set('inCanvas', true);
|
||||
// if (!this.#store.get('hasPointDown')) {
|
||||
// return;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ const infoTextColor = '#ffffff';
|
|||
export const infoFontSize = 10;
|
||||
export const infoLineHeight = 16;
|
||||
|
||||
export const MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE = '@middleware/internal-event/show-info-angle';
|
||||
|
||||
export const defaltStyle: MiddlewareInfoStyle = {
|
||||
textBackground: infoBackground,
|
||||
textColor: infoTextColor
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { PointSize, ViewContext2D } from '@idraw/types';
|
||||
import { rotateByCenter } from '@idraw/util';
|
||||
import type { MiddlewareInfoStyle } from './types';
|
||||
import type { MiddlewareInfoStyle } from '@idraw/types';
|
||||
|
||||
const fontFamily = 'monospace';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,21 @@ import { formatNumber, getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot
|
|||
import { keySelectedElementList, keyActionType, keyGroupQueue } from '../selector';
|
||||
import { drawSizeInfoText, drawPositionInfoText, drawAngleInfoText } from './draw-info';
|
||||
import type { DeepInfoSharedStorage } from './types';
|
||||
import { defaltStyle } from './config';
|
||||
import { defaltStyle, MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE } from './config';
|
||||
|
||||
export { MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE };
|
||||
|
||||
const infoFontSize = 10;
|
||||
const infoLineHeight = 16;
|
||||
|
||||
export const MiddlewareInfo: BoardMiddleware<DeepInfoSharedStorage, CoreEventMap, MiddlewareInfoConfig> = (opts, config) => {
|
||||
const { boardContent, calculator } = opts;
|
||||
export const MiddlewareInfo: BoardMiddleware<
|
||||
DeepInfoSharedStorage,
|
||||
CoreEventMap & {
|
||||
[MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE]: { show: boolean };
|
||||
},
|
||||
MiddlewareInfoConfig
|
||||
> = (opts, config) => {
|
||||
const { boardContent, calculator, eventHub } = opts;
|
||||
const { overlayContext } = boardContent;
|
||||
const innerConfig = {
|
||||
...defaltStyle,
|
||||
|
|
@ -21,9 +29,23 @@ export const MiddlewareInfo: BoardMiddleware<DeepInfoSharedStorage, CoreEventMap
|
|||
textColor
|
||||
};
|
||||
|
||||
let showAngleInfo = true;
|
||||
|
||||
const showInfoAngleCallback = ({ show }: { show: boolean }) => {
|
||||
showAngleInfo = show;
|
||||
};
|
||||
|
||||
return {
|
||||
name: '@middleware/info',
|
||||
|
||||
use() {
|
||||
eventHub.on(MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE, showInfoAngleCallback);
|
||||
},
|
||||
|
||||
disuse() {
|
||||
eventHub.off(MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE, showInfoAngleCallback);
|
||||
},
|
||||
|
||||
beforeDrawFrame({ snapshot }) {
|
||||
const { sharedStore } = snapshot;
|
||||
|
||||
|
|
@ -123,18 +145,20 @@ export const MiddlewareInfo: BoardMiddleware<DeepInfoSharedStorage, CoreEventMap
|
|||
style
|
||||
});
|
||||
|
||||
drawAngleInfoText(overlayContext, {
|
||||
point: {
|
||||
x: rectInfo.top.x + infoFontSize,
|
||||
y: rectInfo.top.y - infoFontSize * 2
|
||||
},
|
||||
rotateCenter: rectInfo.center,
|
||||
angle: totalAngle,
|
||||
text: angleText,
|
||||
fontSize: infoFontSize,
|
||||
lineHeight: infoLineHeight,
|
||||
style
|
||||
});
|
||||
if (showAngleInfo) {
|
||||
drawAngleInfoText(overlayContext, {
|
||||
point: {
|
||||
x: rectInfo.top.x + infoFontSize + 4,
|
||||
y: rectInfo.top.y - infoFontSize * 2 - 18
|
||||
},
|
||||
rotateCenter: rectInfo.center,
|
||||
angle: totalAngle,
|
||||
text: angleText,
|
||||
fontSize: infoFontSize,
|
||||
lineHeight: infoLineHeight,
|
||||
style
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ export const resizeControllerBorderWidth = 4;
|
|||
export const areaBorderWidth = 1;
|
||||
export const controllerSize = 10;
|
||||
|
||||
// export const rotateControllerSize = 16;
|
||||
|
||||
export const rotateControllerSize = 20;
|
||||
export const rotateControllerPosition = 22;
|
||||
|
||||
const activeColor = '#1973ba';
|
||||
const activeAreaColor = '#1976d21c';
|
||||
const lockedColor = '#5b5959b5';
|
||||
|
|
|
|||
49
packages/core/src/middleware/selector/draw-debug.ts
Normal file
49
packages/core/src/middleware/selector/draw-debug.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import type { ViewRectVertexes, ElementSizeController, ViewContext2D, ViewSizeInfo, ViewScaleInfo } from '@idraw/types';
|
||||
import { calcViewPointSize } from '@idraw/util';
|
||||
|
||||
function drawDebugControllerVertexes(opts: {
|
||||
ctx: ViewContext2D;
|
||||
vertexes: ViewRectVertexes;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}): boolean {
|
||||
const { ctx, viewScaleInfo, vertexes } = opts;
|
||||
const v0 = calcViewPointSize(vertexes[0], { viewScaleInfo });
|
||||
const v1 = calcViewPointSize(vertexes[1], { viewScaleInfo });
|
||||
const v2 = calcViewPointSize(vertexes[2], { viewScaleInfo });
|
||||
const v3 = calcViewPointSize(vertexes[3], { viewScaleInfo });
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.fillStyle = '#FF0000A1';
|
||||
ctx.moveTo(v0.x, v0.y);
|
||||
ctx.lineTo(v1.x, v1.y);
|
||||
ctx.lineTo(v2.x, v2.y);
|
||||
ctx.lineTo(v3.x, v3.y);
|
||||
ctx.lineTo(v0.x, v0.y);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function drawDebugStoreSelectedElementController(
|
||||
ctx: ViewContext2D,
|
||||
controller: ElementSizeController | null,
|
||||
opts: {
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
) {
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const { viewSizeInfo, viewScaleInfo } = opts;
|
||||
const { left, right, top, bottom, topLeft, topRight, bottomLeft, bottomRight, rotate } = controller;
|
||||
|
||||
const ctrls = [left, right, top, bottom, topLeft, topRight, bottomLeft, bottomRight, rotate];
|
||||
for (let i = 0; i < ctrls.length; i++) {
|
||||
const ctrl = ctrls[i];
|
||||
drawDebugControllerVertexes({ ctx, vertexes: ctrl.vertexes, viewSizeInfo, viewScaleInfo });
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ import type {
|
|||
} from '@idraw/types';
|
||||
import { rotateElementVertexes, calcViewPointSize, calcViewVertexes, calcViewElementSize } from '@idraw/util';
|
||||
import type { AreaSize } from './types';
|
||||
import { resizeControllerBorderWidth, areaBorderWidth, selectWrapperBorderWidth, controllerSize } from './config';
|
||||
import { drawVertexes, drawLine, drawCircleController, drawCrossVertexes } from './draw-base';
|
||||
import { resizeControllerBorderWidth, areaBorderWidth, selectWrapperBorderWidth } from './config';
|
||||
import { drawVertexes, drawCircleController, drawCrossVertexes } from './draw-base';
|
||||
// import { drawAuxiliaryExperimentBox } from './draw-auxiliary';
|
||||
|
||||
export function drawHoverVertexesWrapper(
|
||||
|
|
@ -81,6 +81,7 @@ export function drawSelectedElementControllersVertexes(
|
|||
element: Element | null;
|
||||
calculator: ViewCalculator;
|
||||
style: MiddlewareSelectorStyle;
|
||||
rotateControllerPattern: ViewContext2D;
|
||||
}
|
||||
) {
|
||||
if (!controller) {
|
||||
|
|
@ -88,12 +89,15 @@ export function drawSelectedElementControllersVertexes(
|
|||
}
|
||||
const {
|
||||
hideControllers,
|
||||
style
|
||||
style,
|
||||
rotateControllerPattern,
|
||||
viewSizeInfo
|
||||
// calculator, element, viewScaleInfo, viewSizeInfo
|
||||
} = opts;
|
||||
|
||||
const { devicePixelRatio = 1 } = viewSizeInfo;
|
||||
const { activeColor } = style;
|
||||
const { elementWrapper, topLeft, topRight, bottomLeft, bottomRight, top, rotate } = controller;
|
||||
const { elementWrapper, topLeft, topRight, bottomLeft, bottomRight, rotate } = controller;
|
||||
const wrapperOpts = { borderColor: activeColor, borderWidth: selectWrapperBorderWidth, background: 'transparent', lineDash: [] };
|
||||
const ctrlOpts = { ...wrapperOpts, borderWidth: resizeControllerBorderWidth, background: '#FFFFFF' };
|
||||
|
||||
|
|
@ -103,12 +107,26 @@ export function drawSelectedElementControllersVertexes(
|
|||
// drawVertexes(ctx, calcViewVertexes(top.vertexes, opts), ctrlOpts);
|
||||
// drawVertexes(ctx, calcViewVertexes(bottom.vertexes, opts), ctrlOpts);
|
||||
if (!hideControllers) {
|
||||
drawLine(ctx, calcViewPointSize(top.center, opts), calcViewPointSize(rotate.center, opts), { ...ctrlOpts, borderWidth: 2 });
|
||||
// drawLine(ctx, calcViewPointSize(top.center, opts), calcViewPointSize(rotate.center, opts), { ...ctrlOpts, borderWidth: 2 });
|
||||
drawVertexes(ctx, calcViewVertexes(topLeft.vertexes, opts), ctrlOpts);
|
||||
drawVertexes(ctx, calcViewVertexes(topRight.vertexes, opts), ctrlOpts);
|
||||
drawVertexes(ctx, calcViewVertexes(bottomLeft.vertexes, opts), ctrlOpts);
|
||||
drawVertexes(ctx, calcViewVertexes(bottomRight.vertexes, opts), ctrlOpts);
|
||||
drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: controllerSize, borderWidth: 2 });
|
||||
|
||||
// TODO
|
||||
drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: rotate.size, borderWidth: 0 });
|
||||
const rotateCenter = calcViewPointSize(rotate.center, opts);
|
||||
ctx.drawImage(
|
||||
rotateControllerPattern.canvas,
|
||||
0,
|
||||
0,
|
||||
rotateControllerPattern.canvas.width / devicePixelRatio,
|
||||
rotateControllerPattern.canvas.height / devicePixelRatio,
|
||||
rotateCenter.x - rotate.size / 2,
|
||||
rotateCenter.y - rotate.size / 2,
|
||||
rotate.size,
|
||||
rotate.size
|
||||
);
|
||||
}
|
||||
|
||||
// drawAuxiliaryExperimentBox(ctx, {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ import {
|
|||
keyEnableSelectInGroup,
|
||||
keyEnableSnapToGrid,
|
||||
controllerSize,
|
||||
rotateControllerSize,
|
||||
rotateControllerPosition,
|
||||
defaultStyle
|
||||
// keyDebugElemCenter,
|
||||
// keyDebugEnd0,
|
||||
|
|
@ -83,11 +85,20 @@ import {
|
|||
import { calcReferenceInfo } from './reference';
|
||||
import { coreEventKeys } from '../../config';
|
||||
import { keyLayoutIsSelected } from '../layout-selector';
|
||||
import { createRotateControllerPattern } from './pattern';
|
||||
import { MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE } from '../info';
|
||||
// import { drawDebugStoreSelectedElementController } from './draw-debug';
|
||||
|
||||
export { keySelectedElementList, keyHoverElement, keyActionType, keyResizeType, keyGroupQueue };
|
||||
export type { DeepSelectorSharedStorage, ActionType };
|
||||
|
||||
export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, CoreEventMap, MiddlewareSelectorConfig> = (opts, config) => {
|
||||
export const MiddlewareSelector: BoardMiddleware<
|
||||
DeepSelectorSharedStorage,
|
||||
CoreEventMap & {
|
||||
[MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE]: { show: boolean };
|
||||
},
|
||||
MiddlewareSelectorConfig
|
||||
> = (opts, config) => {
|
||||
const innerConfig = {
|
||||
...defaultStyle,
|
||||
...config
|
||||
|
|
@ -102,6 +113,11 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
let inBusyMode: 'resize' | 'drag' | 'drag-list' | 'area' | null = null;
|
||||
let hasChangedData: boolean | null = null;
|
||||
|
||||
const rotateControllerPattern = createRotateControllerPattern({
|
||||
fill: style.activeColor,
|
||||
devicePixelRatio: sharer.getActiveViewSizeInfo().devicePixelRatio
|
||||
});
|
||||
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyEnableSnapToGrid, true);
|
||||
|
||||
|
|
@ -140,15 +156,24 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
sharer.setSharedStorage(keyHoverElementVertexes, vertexes);
|
||||
};
|
||||
|
||||
const updateSelectedElementList = (list: Element<ElementType>[], opts?: { triggerEvent?: boolean }) => {
|
||||
sharer.setSharedStorage(keySelectedElementList, list);
|
||||
const updateSelectedElemenetController = () => {
|
||||
const list = sharer.getSharedStorage(keySelectedElementList);
|
||||
if (list.length === 1) {
|
||||
const controller = calcElementSizeController(list[0], {
|
||||
groupQueue: sharer.getSharedStorage(keyGroupQueue),
|
||||
controllerSize,
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo()
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo(),
|
||||
rotateControllerPosition,
|
||||
rotateControllerSize
|
||||
});
|
||||
sharer.setSharedStorage(keySelectedElementController, controller);
|
||||
}
|
||||
};
|
||||
|
||||
const updateSelectedElementList = (list: Element<ElementType>[], opts?: { triggerEvent?: boolean }) => {
|
||||
sharer.setSharedStorage(keySelectedElementList, list);
|
||||
if (list.length === 1) {
|
||||
updateSelectedElemenetController();
|
||||
sharer.setSharedStorage(keySelectedElementPosition, getElementPositionFromList(list[0].uuid, sharer.getActiveStorage('data')?.elements || []));
|
||||
} else {
|
||||
sharer.setSharedStorage(keySelectedElementController, null);
|
||||
|
|
@ -260,6 +285,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
const resizeType = sharer.getSharedStorage(keyResizeType);
|
||||
const actionType = sharer.getSharedStorage(keyActionType);
|
||||
const groupQueue = sharer.getSharedStorage(keyGroupQueue);
|
||||
|
||||
const triggerCursor = (target: PointTarget) => {
|
||||
if (layoutIsSelected === true) {
|
||||
return;
|
||||
|
|
@ -327,6 +353,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
calculator
|
||||
})
|
||||
});
|
||||
|
||||
triggerCursor(target);
|
||||
|
||||
if (target.type === null) {
|
||||
|
|
@ -478,6 +505,9 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
if (actionType === 'drag') {
|
||||
hasChangedData = true;
|
||||
inBusyMode = 'drag';
|
||||
|
||||
eventHub.trigger(MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE, { show: false });
|
||||
|
||||
if (data && elems?.length === 1 && moveOriginalStartElementSize && originalStart && end && elems[0]?.operations?.locked !== true) {
|
||||
const { moveX, moveY } = calcMoveInGroup(originalStart, end, groupQueue);
|
||||
|
||||
|
|
@ -648,6 +678,10 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
moveOriginalStartPoint = null;
|
||||
moveOriginalStartElementSize = null;
|
||||
|
||||
if (actionType === 'drag') {
|
||||
eventHub.trigger(MIDDLEWARE_INTERNAL_EVENT_SHOW_INFO_ANGLE, { show: true });
|
||||
}
|
||||
|
||||
if (actionType === 'resize' && resizeType) {
|
||||
sharer.setSharedStorage(keyResizeType, null);
|
||||
needDrawFrame = true;
|
||||
|
|
@ -720,11 +754,11 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
},
|
||||
|
||||
pointLeave() {
|
||||
prevPoint = null;
|
||||
moveOriginalStartPoint = null;
|
||||
moveOriginalStartElementSize = null;
|
||||
clear();
|
||||
viewer.drawFrame();
|
||||
inBusyMode = null;
|
||||
sharer.setSharedStorage(keyResizeType, null);
|
||||
eventHub.trigger(coreEventKeys.CURSOR, {
|
||||
type: 'default'
|
||||
});
|
||||
},
|
||||
|
||||
doubleClick(e: PointWatcherEvent) {
|
||||
|
|
@ -758,6 +792,13 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
sharer.setSharedStorage(keyActionType, null);
|
||||
},
|
||||
|
||||
wheel() {
|
||||
updateSelectedElemenetController();
|
||||
},
|
||||
wheelScale() {
|
||||
updateSelectedElemenetController();
|
||||
},
|
||||
|
||||
contextMenu: (e: PointWatcherEvent) => {
|
||||
const groupQueue = sharer.getSharedStorage(keyGroupQueue);
|
||||
|
||||
|
|
@ -825,16 +866,8 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
const enableSnapToGrid = sharedStore[keyEnableSnapToGrid];
|
||||
|
||||
const drawBaseOpts = { calculator, viewScaleInfo, viewSizeInfo, style };
|
||||
// const selectedElementController = sharedStore[keySelectedElementController];
|
||||
// const resizeType: ResizeType | null = sharedStore[keyResizeType];
|
||||
|
||||
const selectedElementController = elem
|
||||
? calcElementSizeController(elem, {
|
||||
groupQueue,
|
||||
controllerSize: 10,
|
||||
viewScaleInfo
|
||||
})
|
||||
: null;
|
||||
const selectedElementController = sharedStore[keySelectedElementController];
|
||||
|
||||
const isHoverLocked: boolean = !!hoverElement?.operations?.locked;
|
||||
|
||||
|
|
@ -845,11 +878,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
if (isHoverLocked) {
|
||||
drawLockedVertexesWrapper(overlayContext, hoverElementVertexes, {
|
||||
...drawBaseOpts,
|
||||
controller: calcElementSizeController(hoverElement, {
|
||||
groupQueue,
|
||||
controllerSize: 10,
|
||||
viewScaleInfo
|
||||
}),
|
||||
controller: selectedElementController,
|
||||
style
|
||||
});
|
||||
} else {
|
||||
|
|
@ -862,6 +891,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
element: elem,
|
||||
calculator,
|
||||
hideControllers: !!isMoving && actionType === 'drag',
|
||||
rotateControllerPattern,
|
||||
style
|
||||
});
|
||||
if (actionType === 'drag') {
|
||||
|
|
@ -892,11 +922,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
if (isHoverLocked) {
|
||||
drawLockedVertexesWrapper(overlayContext, hoverElementVertexes, {
|
||||
...drawBaseOpts,
|
||||
controller: calcElementSizeController(hoverElement, {
|
||||
groupQueue,
|
||||
controllerSize: 10,
|
||||
viewScaleInfo
|
||||
}),
|
||||
controller: selectedElementController,
|
||||
style
|
||||
});
|
||||
} else {
|
||||
|
|
@ -909,6 +935,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
element: elem,
|
||||
calculator,
|
||||
hideControllers: !!isMoving && actionType === 'drag',
|
||||
rotateControllerPattern,
|
||||
style
|
||||
});
|
||||
if (actionType === 'drag') {
|
||||
|
|
@ -946,6 +973,12 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
}
|
||||
}
|
||||
|
||||
// // TODO: debug
|
||||
// drawDebugStoreSelectedElementController(overlayContext, sharer.getSharedStorage(keySelectedElementController), {
|
||||
// viewScaleInfo,
|
||||
// viewSizeInfo
|
||||
// });
|
||||
|
||||
// // TODO mock data
|
||||
// const elemCenter: any = sharer.getSharedStorage(keyDebugElemCenter);
|
||||
// const startVertical = sharer.getSharedStorage(keyDebugStartVertical);
|
||||
|
|
|
|||
102
packages/core/src/middleware/selector/pattern/icon-rotate.ts
Normal file
102
packages/core/src/middleware/selector/pattern/icon-rotate.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import type { Element } from '@idraw/types';
|
||||
import { createUUID } from '@idraw/util';
|
||||
|
||||
export const createIconRotate = (opts?: { fill?: string }) => {
|
||||
const iconRotate: Element<'path'> = {
|
||||
uuid: createUUID(),
|
||||
type: 'path',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 200,
|
||||
h: 200,
|
||||
detail: {
|
||||
commands: [
|
||||
{
|
||||
type: 'M',
|
||||
params: [512, 0]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [282.8, 0, 512, 229.2, 512, 512]
|
||||
},
|
||||
{
|
||||
type: 's',
|
||||
params: [-229.2, 512, -512, 512]
|
||||
},
|
||||
{
|
||||
type: 'S',
|
||||
params: [0, 794.8, 0, 512, 229.2, 0, 512, 0]
|
||||
},
|
||||
{
|
||||
type: 'z',
|
||||
params: []
|
||||
},
|
||||
{
|
||||
type: 'm',
|
||||
params: [309.8, 253.8]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [0, -10.5, -6.5, -19.8, -15.7, -23.8, -9.7, -4, -21, -2, -28.2, 5.6]
|
||||
},
|
||||
{
|
||||
type: 'l',
|
||||
params: [-52.5, 52]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [
|
||||
-56.9, -53.7, -133.9, -85.5, -213.4, -85.5, -170.7, 0, -309.8, 139.2, -309.8, 309.8, 0, 170.6, 139.2, 309.8, 309.8, 309.8, 92.4, 0, 179.5, -40.8,
|
||||
238.4, -111.8, 4, -5.2, 4, -12.9, -0.8, -17.3
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'L',
|
||||
params: [694.3, 637]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [
|
||||
-2.8, -2.4, -6.5, -3.6, -10.1, -3.6, -3.6, 0.4, -7.3, 2, -9.3, 4.8, -39.5, 51.2, -98.8, 80.3, -163, 80.3, -113.8, 0, -206.5, -92.8, -206.5, -206.5,
|
||||
0, -113.8, 92.8, -206.5, 206.5, -206.5, 52.8, 0, 102.9, 20.2, 140.8, 55.3
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'L',
|
||||
params: [597, 416.5]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [-7.7, 7.3, -9.7, 18.6, -5.6, 27.9, 4, 9.7, 13.3, 16.1, 23.8, 16.1]
|
||||
},
|
||||
{
|
||||
type: 'H',
|
||||
params: [796]
|
||||
},
|
||||
{
|
||||
type: 'c',
|
||||
params: [14.1, 0, 25.8, -11.7, 25.8, -25.8]
|
||||
},
|
||||
{
|
||||
type: 'V',
|
||||
params: [253.8]
|
||||
},
|
||||
{
|
||||
type: 'z',
|
||||
params: []
|
||||
}
|
||||
],
|
||||
fill: '#2c2c2c',
|
||||
stroke: 'transparent',
|
||||
strokeWidth: 0,
|
||||
originX: 0,
|
||||
originY: 0,
|
||||
originW: 1024,
|
||||
originH: 1024,
|
||||
opacity: 1,
|
||||
...opts
|
||||
}
|
||||
};
|
||||
|
||||
return iconRotate;
|
||||
};
|
||||
45
packages/core/src/middleware/selector/pattern/index.ts
Normal file
45
packages/core/src/middleware/selector/pattern/index.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import type { ViewContext2D } from '@idraw/types';
|
||||
import { createOffscreenContext2D } from '@idraw/util';
|
||||
import { drawElement } from '@idraw/renderer';
|
||||
import { createIconRotate } from './icon-rotate';
|
||||
|
||||
export function createRotateControllerPattern(opts: { fill: string; devicePixelRatio: number }): ViewContext2D {
|
||||
const { fill, devicePixelRatio } = opts;
|
||||
const iconRotate = createIconRotate({ fill });
|
||||
const { w, h } = iconRotate;
|
||||
const context2d = createOffscreenContext2D({
|
||||
width: w,
|
||||
height: h,
|
||||
devicePixelRatio
|
||||
});
|
||||
|
||||
// context2d.fillStyle = 'red'; // TODO
|
||||
// context2d.fillRect(0, 0, size, size);
|
||||
|
||||
drawElement(context2d, iconRotate, {
|
||||
loader: undefined as any,
|
||||
viewScaleInfo: {
|
||||
scale: 1,
|
||||
offsetTop: 0,
|
||||
offsetBottom: 0,
|
||||
offsetLeft: 0,
|
||||
offsetRight: 0
|
||||
},
|
||||
viewSizeInfo: {
|
||||
width: w,
|
||||
height: h,
|
||||
devicePixelRatio,
|
||||
contextWidth: w,
|
||||
contextHeight: h
|
||||
},
|
||||
parentElementSize: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w,
|
||||
h
|
||||
},
|
||||
parentOpacity: 1
|
||||
});
|
||||
|
||||
return context2d;
|
||||
}
|
||||
|
|
@ -839,7 +839,7 @@ export function rotateElement(
|
|||
});
|
||||
const startAngle = limitAngle(angle);
|
||||
const changedRadian = calcRadian(elemCenter, start, end);
|
||||
const endAngle = startAngle + parseRadianToAngle(changedRadian);
|
||||
const endAngle = limitAngle(startAngle + parseRadianToAngle(changedRadian));
|
||||
|
||||
return {
|
||||
x,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export { drawImage } from './image';
|
|||
export { drawSVG } from './svg';
|
||||
export { drawHTML } from './html';
|
||||
export { drawText } from './text';
|
||||
export { drawGroup, drawElement } from './group';
|
||||
export { drawElementList } from './elements';
|
||||
export { drawLayout } from './layout';
|
||||
export { drawGlobalBackground } from './global';
|
||||
|
|
|
|||
|
|
@ -123,4 +123,16 @@ export class Renderer extends EventEmitter<RendererEventMap> implements BoardRen
|
|||
}
|
||||
}
|
||||
|
||||
export { drawRect } from './draw';
|
||||
export {
|
||||
drawCircle,
|
||||
drawRect,
|
||||
drawImage,
|
||||
drawSVG,
|
||||
drawHTML,
|
||||
drawText,
|
||||
drawGroup,
|
||||
drawElement,
|
||||
drawElementList,
|
||||
drawLayout,
|
||||
drawGlobalBackground
|
||||
} from './draw';
|
||||
|
|
|
|||
|
|
@ -169,5 +169,6 @@ export interface BoardWatcherOptions {
|
|||
|
||||
export interface BoardWatcherStore {
|
||||
hasPointDown: boolean;
|
||||
inCanvas: boolean;
|
||||
prevClickPoint: Point | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface ElementSizeControllerItem {
|
|||
type: ElementSizeControllerType;
|
||||
vertexes: ViewRectVertexes;
|
||||
center: PointSize;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ElementSizeController {
|
||||
|
|
|
|||
|
|
@ -21,15 +21,20 @@ export function calcElementSizeController(
|
|||
elemSize: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
controllerSize?: number;
|
||||
controllerSize: number;
|
||||
rotateControllerSize: number;
|
||||
rotateControllerPosition: number;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
): ElementSizeController {
|
||||
const { groupQueue, controllerSize, viewScaleInfo } = opts;
|
||||
const { groupQueue, controllerSize, viewScaleInfo, rotateControllerSize, rotateControllerPosition } = opts;
|
||||
|
||||
const ctrlSize = (controllerSize && controllerSize > 0 ? controllerSize : 8) / viewScaleInfo.scale;
|
||||
const { x, y, w, h, angle = 0 } = elemSize;
|
||||
|
||||
const rotateCtrlSize = rotateControllerSize;
|
||||
const rotateCtrlPos = rotateControllerPosition;
|
||||
|
||||
const ctrlGroupQueue = [
|
||||
...[
|
||||
{
|
||||
|
|
@ -53,10 +58,10 @@ export function calcElementSizeController(
|
|||
const vertexes = calcElementVertexesInGroup(elemSize, { groupQueue }) as ViewRectVertexes;
|
||||
const rotateElemVertexes = calcElementVertexesInGroup(
|
||||
{
|
||||
x: x - ctrlSize * 2,
|
||||
y: y - ctrlSize * 2,
|
||||
h: h + ctrlSize * 4,
|
||||
w: w + ctrlSize * 4,
|
||||
x: x,
|
||||
y: y - (rotateCtrlPos + rotateCtrlSize / 2) / viewScaleInfo.scale,
|
||||
h: h + (rotateCtrlPos * 2 + rotateCtrlSize) / viewScaleInfo.scale,
|
||||
w: w,
|
||||
angle
|
||||
},
|
||||
{ groupQueue: [...groupQueue] }
|
||||
|
|
@ -97,92 +102,94 @@ export function calcElementSizeController(
|
|||
const bottomMiddleVertexes = calcElementVertexes(bottomMiddleSize);
|
||||
const leftMiddleVertexes = calcElementVertexes(leftMiddleSize);
|
||||
|
||||
// const originRotateCenter: PointSize = {
|
||||
// x: x + w / 2,
|
||||
// y: y - ctrlSize * 4
|
||||
// };
|
||||
|
||||
// const rotateCenter = topCenter;
|
||||
const rotateCenter = getCenterFromTwoPoints(rotateElemVertexes[0], rotateElemVertexes[1]);
|
||||
const rotateSize = createControllerElementSizeFromCenter(rotateCenter, { size: ctrlSize, angle: totalAngle });
|
||||
// TODO
|
||||
const tempRotateSizeRepairRatio = 1.1;
|
||||
const rotateSize = createControllerElementSizeFromCenter(rotateCenter, {
|
||||
size: (rotateControllerSize * tempRotateSizeRepairRatio) / viewScaleInfo.scale,
|
||||
angle: totalAngle
|
||||
});
|
||||
const rotateVertexes = calcElementVertexes(rotateSize);
|
||||
|
||||
// const rotateCtrlElem: ElementSize = {
|
||||
// x: originRotateCenter.x - ctrlSize / 2,
|
||||
// y: originRotateCenter.x - ctrlSize / 2,
|
||||
// w: ctrlSize,
|
||||
// h: ctrlSize,
|
||||
// angle
|
||||
// };
|
||||
// const rotateVertexes = calcElementVertexesInGroup(rotateCtrlElem, { groupQueue }) as ViewRectVertexes;
|
||||
// const rotateCenter = getCenterFromTwoPoints(rotateVertexes[0], rotateVertexes[2]);
|
||||
|
||||
const sizeController: ElementSizeController = {
|
||||
elementWrapper: vertexes,
|
||||
left: {
|
||||
type: 'left',
|
||||
vertexes: leftVertexes,
|
||||
center: leftCenter
|
||||
center: leftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
right: {
|
||||
type: 'right',
|
||||
vertexes: rightVertexes,
|
||||
center: rightCenter
|
||||
center: rightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
top: {
|
||||
type: 'top',
|
||||
vertexes: topVertexes,
|
||||
center: topCenter
|
||||
center: topCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottom: {
|
||||
type: 'bottom',
|
||||
vertexes: bottomVertexes,
|
||||
center: bottomCenter
|
||||
center: bottomCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topLeft: {
|
||||
type: 'top-left',
|
||||
vertexes: topLeftVertexes,
|
||||
center: topLeftCenter
|
||||
center: topLeftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topRight: {
|
||||
type: 'top-right',
|
||||
vertexes: topRightVertexes,
|
||||
center: topRightCenter
|
||||
center: topRightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomLeft: {
|
||||
type: 'bottom-left',
|
||||
vertexes: bottomLeftVertexes,
|
||||
center: bottomLeftCenter
|
||||
center: bottomLeftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomRight: {
|
||||
type: 'bottom-right',
|
||||
vertexes: bottomRightVertexes,
|
||||
center: bottomRightCenter
|
||||
center: bottomRightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
leftMiddle: {
|
||||
type: 'left-middle',
|
||||
vertexes: leftMiddleVertexes,
|
||||
center: leftCenter
|
||||
center: leftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
rightMiddle: {
|
||||
type: 'right-middle',
|
||||
vertexes: rightMiddleVertexes,
|
||||
center: rightCenter
|
||||
center: rightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topMiddle: {
|
||||
type: 'top-middle',
|
||||
vertexes: topMiddleVertexes,
|
||||
center: topCenter
|
||||
center: topCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomMiddle: {
|
||||
type: 'bottom-middle',
|
||||
vertexes: bottomMiddleVertexes,
|
||||
center: bottomCenter
|
||||
center: bottomCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
rotate: {
|
||||
type: 'rotate',
|
||||
vertexes: rotateVertexes,
|
||||
center: rotateCenter
|
||||
center: rotateCenter,
|
||||
size: rotateControllerSize
|
||||
}
|
||||
};
|
||||
return sizeController;
|
||||
|
|
@ -241,62 +248,74 @@ export function calcLayoutSizeController(
|
|||
left: {
|
||||
type: 'left',
|
||||
vertexes: leftVertexes,
|
||||
center: leftCenter
|
||||
center: leftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
right: {
|
||||
type: 'right',
|
||||
vertexes: rightVertexes,
|
||||
center: rightCenter
|
||||
center: rightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
top: {
|
||||
type: 'top',
|
||||
vertexes: topVertexes,
|
||||
center: topCenter
|
||||
center: topCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottom: {
|
||||
type: 'bottom',
|
||||
vertexes: bottomVertexes,
|
||||
center: bottomCenter
|
||||
center: bottomCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topLeft: {
|
||||
type: 'top-left',
|
||||
vertexes: topLeftVertexes,
|
||||
center: topLeftCenter
|
||||
center: topLeftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topRight: {
|
||||
type: 'top-right',
|
||||
vertexes: topRightVertexes,
|
||||
center: topRightCenter
|
||||
center: topRightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomLeft: {
|
||||
type: 'bottom-left',
|
||||
vertexes: bottomLeftVertexes,
|
||||
center: bottomLeftCenter
|
||||
center: bottomLeftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomRight: {
|
||||
type: 'bottom-right',
|
||||
vertexes: bottomRightVertexes,
|
||||
center: bottomRightCenter
|
||||
center: bottomRightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
leftMiddle: {
|
||||
type: 'left-middle',
|
||||
vertexes: leftMiddleVertexes,
|
||||
center: leftCenter
|
||||
center: leftCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
rightMiddle: {
|
||||
type: 'right-middle',
|
||||
vertexes: rightMiddleVertexes,
|
||||
center: rightCenter
|
||||
center: rightCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
topMiddle: {
|
||||
type: 'top-middle',
|
||||
vertexes: topMiddleVertexes,
|
||||
center: topCenter
|
||||
center: topCenter,
|
||||
size: ctrlSize
|
||||
},
|
||||
bottomMiddle: {
|
||||
type: 'bottom-middle',
|
||||
vertexes: bottomMiddleVertexes,
|
||||
center: bottomCenter
|
||||
center: bottomCenter,
|
||||
size: ctrlSize
|
||||
}
|
||||
};
|
||||
return sizeController;
|
||||
|
|
|
|||
|
|
@ -72,16 +72,18 @@ export function calcElementCenterFromVertexes(ves: ViewRectVertexes): PointSize
|
|||
}
|
||||
|
||||
export function calcRadian(center: PointSize, start: PointSize, end: PointSize): number {
|
||||
const startAngle = calcLineRadian(center, start);
|
||||
const endAngle = calcLineRadian(center, end);
|
||||
if (endAngle !== null && startAngle !== null) {
|
||||
if (startAngle > (Math.PI * 3) / 2 && endAngle < Math.PI / 2) {
|
||||
return endAngle + (Math.PI * 2 - startAngle);
|
||||
} else if (endAngle > (Math.PI * 3) / 2 && startAngle < Math.PI / 2) {
|
||||
return startAngle + (Math.PI * 2 - endAngle);
|
||||
} else {
|
||||
return endAngle - startAngle;
|
||||
}
|
||||
const startRadian = calcLineRadian(center, start);
|
||||
const endRadian = calcLineRadian(center, end);
|
||||
|
||||
if (endRadian !== null && startRadian !== null) {
|
||||
// if (startRadian > (Math.PI * 3) / 2 && endRadian < Math.PI / 2) {
|
||||
// return endRadian + (Math.PI * 2 - startRadian);
|
||||
// } else if (endRadian > (Math.PI * 3) / 2 && startRadian < Math.PI / 2) {
|
||||
// return startRadian + (Math.PI * 2 - endRadian);
|
||||
// } else {
|
||||
// return endRadian - startRadian;
|
||||
// }
|
||||
return endRadian - startRadian;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -221,12 +223,14 @@ export function rotateVertexes(center: PointSize, ves: ViewRectVertexes, radian:
|
|||
|
||||
// [0, 360], eg. 370 to 10, -10 to 350
|
||||
export function limitAngle(angle: number): number {
|
||||
if (!(angle > 0 || angle < 0) || angle === 0) {
|
||||
if (!(angle > 0 || angle < 0) || angle === 0 || angle === 360) {
|
||||
return 0;
|
||||
}
|
||||
let num = angle % 360;
|
||||
if (num < 0) {
|
||||
num += 360;
|
||||
} else if (angle === 360) {
|
||||
num = 0;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue