import { Layer } from './layer';
import { RulerConfigEntity, PlaygroundConfigEntity, SCALE_WIDTH, SnaplineConfigEntity, Snapline } from './config';
import { Cache, PositionSchema } from '@gedit/utils';
import { domUtils } from '@gedit/utils/lib/browser';
import { entity } from '../../common';
import { nanoid } from 'nanoid';
const SCALE_UNIT_LEN = SCALE_WIDTH * 0.7;
interface RulerScaleUnit {
realSize: number,
renderSize: number,
zoom: number,
}
let index = 0;
/**
* 标尺及网格
*/
export class RulerLayer extends Layer {
@entity(PlaygroundConfigEntity) protected playgroundConfigEntity: PlaygroundConfigEntity;
@entity(RulerConfigEntity) protected rulerConfigEntity: RulerConfigEntity;
@entity(SnaplineConfigEntity) protected snaplineEntity: SnaplineConfigEntity;
protected readonly weakCache = Cache.createWeakCache();
protected readonly index = index++;
protected grid?: HTMLElement;
protected scaleX?: HTMLElement;
// protected xCurrentRuler: HTMLElement;
protected scaleY?: HTMLElement;
// protected yCurrentRuler: HTMLElement;
private _scaleYContent = '';
private _scaleXContent = '';
private _scaleXTextContent = '';
private _scaleYTextContent = '';
private _gridContent = '';
protected currentSelectLine: Snapline | undefined;
readonly node = domUtils.createDivWithClass('gedit-ruler');
onReady(): void {
this.toDispose.pushAll([
this.listenPlaygroundEvent('mouseleave', () => {
if (this.currentSelectLine) {
document.body.className = `${document.body.className || ''} not-allowed`;
}
}),
this.listenGlobalEvent('mouseup', () => {
if (this.currentSelectLine) {
document.body.className = document.body.className.replace(/not-allowed/ig, '').trim();
// console.log('up', document.body.className);
const newLines = this.snaplineEntity.config.lines.filter(c => c.id !== this.currentSelectLine!.id);
this.snaplineEntity.updateConfig({
lines: newLines,
});
this.currentSelectLine = undefined;
}
})
]);
}
protected scrollToView(e: Event): void {
const originClickFn = this.rulerConfigEntity.getOriginClickFn();
if (originClickFn) {
originClickFn();
} else {
this.playgroundConfigEntity.scrollPageBoundsToCenter();
}
e.stopPropagation();
e.preventDefault();
}
/**
* 绘制标尺X
*/
protected drawScaleX(unit: RulerScaleUnit): void {
const id = this.index;
if (!this.scaleX) return;
const minor = unit.renderSize;
const newContent = `
`;
if (newContent !== this._scaleXContent) {
this._scaleXContent = newContent;
this.scaleX.innerHTML = newContent;
}
}
/**
* 绘制标尺Y
*/
protected drawScaleY(unit: RulerScaleUnit): void {
const id = this.index;
if (!this.scaleY) return;
const minor = unit.renderSize;
const newContent = `
`;
if (newContent !== this._scaleYContent) {
this._scaleYContent = newContent;
this.scaleY.innerHTML = newContent;
}
}
/**
* 当前缩放比
*/
get zoom(): number {
return this.config.finalScale;
}
/**
* 最小单元格大小
*/
getScaleUnit(): RulerScaleUnit {
const zoom = this.zoom;
let realSize = 1;
if (this.zoom > 1) {
// 放大模式
realSize = this.zoom >= 5 ? 1 : 10;
} else {
// 缩小模式
realSize = Math.round(1 / this.zoom) * 10;
}
return {
realSize, // 一个单元格代表的真实大小
renderSize: Math.round(realSize * zoom * 100) / 100, // 一个单元格渲染的大小值
zoom, // 缩放比
};
}
/**
* 绘制网格
*/
protected drawGrid(unit: RulerScaleUnit): void {
const id = this.index;
const minor = unit.renderSize;
const medium = minor * 5;
const major = minor * 10;
if (!this.grid) return;
const newContent = `
`;
if (newContent !== this._gridContent) {
this._gridContent = newContent;
this.grid.innerHTML = newContent;
}
}
protected drawScaleText(width: number, height: number, scaleOrigin: PositionSchema, scaleUnit: RulerScaleUnit): void {
const fw = Math.ceil(width);
const fh = Math.ceil(height);
const interval = scaleUnit.renderSize * 10 + 1; // 间距
const xCount = Math.ceil(fw / interval);
const yCount = Math.ceil(fh / interval);
const firstX = -Math.round(scaleOrigin.x / interval);
const firstY = -Math.round(scaleOrigin.y / interval);
if (this.scaleX) {
const scaleXTextElement = this.scaleX.querySelector(`#gedit-scale${this.index}-x`)!;
const textList = [];
for (let i = 0, len = xCount; i < len; i ++) {
const num = i + firstX;
const x = i * interval + 14;
const y = 14;
const text = num * scaleUnit.realSize * 10;
textList.push(`${text}`);
}
const newContent = textList.join('');
if (this._scaleXTextContent !== newContent) {
this._scaleXTextContent = newContent;
scaleXTextElement.innerHTML = newContent;
}
}
if (this.scaleY) {
const scaleYTextElement = this.scaleY.querySelector(`#gedit-scale${this.index}-y`)!;
const textList = [];
for (let i = 0, len = yCount; i < len; i ++) {
const num = i + firstY;
const x = 14;
const y = i * interval - 14;
const transform = `rotate(-90, 14, ${y})`;
const text = num * scaleUnit.realSize * 10;
textList.push(`${text}`);
}
const newContent = textList.join('');
if (this._scaleYTextContent !== newContent) {
this._scaleYTextContent = newContent;
scaleYTextElement.innerHTML = newContent;
}
}
}
protected setSVGStyle(svgElement: HTMLElement | undefined, style: { width: number, height: number, visible: boolean, left: number, top: number }): void {
if (!svgElement) return;
if (!this.weakCache.isChanged(svgElement, style)) return;
this.weakCache.save(svgElement, style);
if (style.visible) {
svgElement.classList.remove('hidden');
} else {
svgElement.classList.add('hidden');
return;
}
svgElement.style.width = style.width + 'px';
svgElement.style.height = style.height + 'px';
svgElement.style.left = style.left + 'px';
svgElement.style.top = style.top + 'px';
}
/**
* 获取标尺的滚动距离
* @param realScroll
* @param mod
*/
protected getScrollDelta(realScroll: number, mod: number): number {
// 正向滚动不用补差
if (realScroll >= 0) {
return realScroll % mod;
}
return mod - Math.abs(realScroll) % mod;
}
protected addScaleEvent(type: 'vertical' | 'horizontal', dom: HTMLElement): void {
const moveId = `${type}_move`;
const createMoveLine = (mouse: PositionSchema): Snapline => {
const moveLine: Snapline = {
id: moveId,
color: 'red',
strokeWidth: 1,
start: mouse,
isMove: true
};
moveLine[type] = true;
return moveLine;
};
const stopEvent = (e: MouseEvent) => {
e.stopPropagation();
e.preventDefault();
};
const mouseenter = (e: MouseEvent) => {
const mouse = this.getPosFromMouseEvent(e);
const currentLine = this.snaplineEntity.config.lines.find(c =>
c.isMove || Math.abs(c.start.x - mouse.x) < 1 && Math.abs(c.start.y - mouse.y) < 1
);
if (!currentLine) {
this.snaplineEntity.updateConfig({
lines: [...this.snaplineEntity.config.lines, createMoveLine(mouse)]
});
}
};
const mouseleave = () => {
const lines = this.snaplineEntity.config.lines;
const nextLines = lines.filter(c => !c.isMove);
this.snaplineEntity.updateConfig({
lines: nextLines,
});
};
const mousemove = (e: MouseEvent) => {
stopEvent(e);
const mouse = this.getPosFromMouseEvent(e);
let otherLines;
if (!this.currentSelectLine) {
let moveLine: Snapline | [] = this.snaplineEntity.config.lines.find(c => c.isMove) || createMoveLine(mouse);
otherLines = this.snaplineEntity.config.lines.filter( c => !c.isMove);
moveLine.start = mouse;
const currentLines = this.snaplineEntity.config.lines.find(
c => {
const t = type === 'vertical' ? 'x' : 'y';
return Math.abs(c.start[t] - mouse[t]) < 1 && !c.isMove;
}
);
if (currentLines) {
moveLine = [];
}
this.snaplineEntity.updateConfig({lines: otherLines.concat(moveLine)});
return;
}
otherLines = this.snaplineEntity.config.lines.filter(c => c.id !== this.currentSelectLine!.id && !c.isMove);
this.currentSelectLine.start = mouse;
this.snaplineEntity.updateConfig({ lines: [...otherLines, this.currentSelectLine]});
};
const mousedown = (e: MouseEvent) => {
stopEvent(e);
const mouse = this.getPosFromMouseEvent(e);
// console.log(mouse, this.snaplineEntity.config.lines);
const currentLines = this.snaplineEntity.config.lines.find(
c => {
const t = type === 'vertical' ? 'x' : 'y';
return Math.abs(c.start[t] - mouse[t]) < 1 && !c.isMove;
}
);
if (currentLines) {
// console.log('current');
this.currentSelectLine = currentLines;
const nextLines = this.snaplineEntity.config.lines.filter(c => !c.isMove && c !== currentLines);
this.snaplineEntity.updateConfig({
lines: [...nextLines, {...currentLines, start: mouse}],
});
return;
}
const line: Snapline = {
id: nanoid(),
color: 'red',
strokeWidth: 1,
start: mouse
};
line[type] = true;
this.currentSelectLine = line;
this.snaplineEntity.updateConfig({
lines: this.snaplineEntity.config.lines.concat(line),
});
};
const mouseup = (e: MouseEvent) => {
stopEvent(e);
this.currentSelectLine = undefined;
};
dom.removeEventListener('mouseenter', mouseenter);
dom.removeEventListener('mouseleave', mouseleave);
dom.removeEventListener('mousemove', mousemove);
dom.removeEventListener('mousedown', mousedown);
dom.removeEventListener('mouseup', mouseup);
dom.addEventListener('mouseenter', mouseenter);
dom.addEventListener('mouseleave', mouseleave);
dom.addEventListener('mousemove', mousemove);
dom.addEventListener('mousedown', mousedown);
dom.addEventListener('mouseup', mouseup);
}
/**
* 绘制
*/
draw(): void {
const playgroundConfig = this.playgroundConfigEntity.config;
const rulerConfig = this.rulerConfigEntity.config;
const scaleUnit = this.getScaleUnit();
this.createElements();
const mod = scaleUnit.renderSize * 10;
const viewBoxWidth = playgroundConfig.width + mod * 2;
const viewBoxHeight = playgroundConfig.height + mod * 2;
const scrollX = playgroundConfig.scrollX;
const scrollY = playgroundConfig.scrollY;
const scrollXDelta = this.getScrollDelta(scrollX, mod);
const scrollYDelta = this.getScrollDelta(scrollY, mod);
domUtils.setStyle(this.node, {
left: scrollX - SCALE_WIDTH,
top: scrollY - SCALE_WIDTH
});
if (rulerConfig.gridVisible) {
this.drawGrid(scaleUnit);
}
if (rulerConfig.scaleVisible) {
this.drawScaleX(scaleUnit);
this.drawScaleY(scaleUnit);
// 原点在标尺中的位置
const scaleOrigin = {
x: -scrollX + scrollXDelta + mod,
y: -scrollY + scrollYDelta + mod,
};
this.drawScaleText(viewBoxWidth, viewBoxHeight, scaleOrigin, scaleUnit);
}
// 设置网格
this.setSVGStyle(this.grid, {
width: viewBoxWidth,
height: viewBoxHeight,
visible: rulerConfig.gridVisible,
left: SCALE_WIDTH - scrollXDelta - mod,
top: SCALE_WIDTH - scrollYDelta - mod,
});
// 设置标尺x
this.setSVGStyle(this.scaleX, {
width: viewBoxWidth,
height: SCALE_WIDTH,
visible: rulerConfig.scaleVisible,
left: SCALE_WIDTH - scrollXDelta - mod,
top: 0
});
// 设置标尺y
this.setSVGStyle(this.scaleY, {
width: SCALE_WIDTH,
height: viewBoxHeight,
visible: rulerConfig.scaleVisible,
left: 0,
top: SCALE_WIDTH - scrollYDelta - mod
});
}
private _created = false;
createElements(): void {
if (this._created) return;
this.node.innerHTML = `
`;
const gridDom: HTMLElement = document.createElement('div');
const scaleXDom: HTMLElement = document.createElement('div');
const scaleYDom: HTMLElement = document.createElement('div');
scaleXDom.className = 'gedit-scale-svg scale-x hidden';
scaleYDom.className = 'gedit-scale-svg scale-y hidden';
gridDom.className = 'gedit-grid-svg hidden';
this.node.appendChild(gridDom);
this.node.appendChild(scaleXDom);
this.node.appendChild(scaleYDom);
this.scaleX = scaleXDom;
this.scaleY = scaleYDom;
this.grid = gridDom;
// 标尺 hover 效果
this.addScaleEvent('vertical', this.scaleX);
this.addScaleEvent('horizontal', this.scaleY);
this.node.querySelector(`#gedit-ruler${this.id}-holder`)!.addEventListener('mousedown', e => this.scrollToView(e));
this._created = true;
}
}