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; } }