From de0ceebee6d727e02b8f38fe4819106ca33190f8 Mon Sep 17 00:00:00 2001 From: redhoodsu Date: Fri, 16 Feb 2024 13:38:29 +0800 Subject: [PATCH] feat(painter): brush cursor --- src/painter/icon.css | 23 ++++++------ src/painter/icon/crosshair.svg | 6 ++++ src/painter/index.ts | 13 +++---- src/painter/style.scss | 1 + src/painter/tools/Brush.ts | 29 +++++++++++---- src/painter/tools/Eraser.ts | 17 +++++++++ src/painter/tools/Pencil.ts | 65 ++++++++++++++++++++++++++++++++-- src/painter/tools/Tool.ts | 22 +++++++----- src/painter/tools/Zoom.ts | 10 ++++-- 9 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 src/painter/icon/crosshair.svg diff --git a/src/painter/icon.css b/src/painter/icon.css index 2747e44..1647a62 100644 --- a/src/painter/icon.css +++ b/src/painter/icon.css @@ -1,6 +1,6 @@ @font-face { font-family: 'luna-painter-icon'; - src: url('data:application/x-font-woff;charset=utf-8;base64,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') + src: url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'); } @@ -17,30 +17,33 @@ .icon-brush:before { content: '\f101'; } -.icon-eraser:before { +.icon-crosshair:before { content: '\f102'; } -.icon-hand:before { +.icon-eraser:before { content: '\f103'; } -.icon-paint-bucket:before { +.icon-hand:before { content: '\f104'; } -.icon-pencil:before { +.icon-paint-bucket:before { content: '\f105'; } -.icon-reset-color:before { +.icon-pencil:before { content: '\f106'; } -.icon-swap:before { +.icon-reset-color:before { content: '\f107'; } -.icon-zoom-in:before { +.icon-swap:before { content: '\f108'; } -.icon-zoom-out:before { +.icon-zoom-in:before { content: '\f109'; } -.icon-zoom:before { +.icon-zoom-out:before { content: '\f10a'; } +.icon-zoom:before { + content: '\f10b'; +} diff --git a/src/painter/icon/crosshair.svg b/src/painter/icon/crosshair.svg new file mode 100644 index 0000000..37ffd37 --- /dev/null +++ b/src/painter/icon/crosshair.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/src/painter/index.ts b/src/painter/index.ts index 3b8b5f0..50dcc1a 100644 --- a/src/painter/index.ts +++ b/src/painter/index.ts @@ -43,7 +43,6 @@ export default class Painter extends Component { private eraser: Eraser private activeLayer: Layer private resizeSensor: ResizeSensor - private canvasResizeSenor: ResizeSensor private $foregroundColor: $.$ private $backgroundColor: $.$ constructor(container: HTMLElement, options: IOptions = {}) { @@ -72,13 +71,10 @@ export default class Painter extends Component { this.ctx = this.canvas.getContext('2d')! this.resizeSensor = new ResizeSensor(container) - this.canvasResizeSenor = new ResizeSensor(this.canvas) this.addLayer() this.activeLayer = this.layers[0] - this.bindEvent() - this.brush = new Brush(this) this.pencil = new Pencil(this) this.hand = new Hand(this) @@ -86,6 +82,8 @@ export default class Painter extends Component { this.paintBucket = new PaintBucket(this) this.eraser = new Eraser(this) + this.bindEvent() + this.resetViewport() this.hand.centerCanvas() @@ -94,7 +92,6 @@ export default class Painter extends Component { destroy() { super.destroy() this.resizeSensor.destroy() - this.canvasResizeSenor.destroy() } /** Add layer. */ addLayer() { @@ -239,7 +236,11 @@ export default class Painter extends Component { }) this.resizeSensor.addListener(this.onResize) - this.canvasResizeSenor.addListener(this.resetViewport) + + this.zoom.on('change', () => { + this.currentTool.onZoom() + this.resetViewport() + }) } private onViewportMouseEnter = (e: any) => { this.currentTool.onMouseEnter(e.origEvent) diff --git a/src/painter/style.scss b/src/painter/style.scss index 7954295..2749440 100644 --- a/src/painter/style.scss +++ b/src/painter/style.scss @@ -134,6 +134,7 @@ opacity: 0; left: 0; top: 0; + transform: translate(-50%, -50%); .icon { color: #000; text-shadow: -1px -1px 0 $color-white, 1px -1px 0 $color-white, diff --git a/src/painter/tools/Brush.ts b/src/painter/tools/Brush.ts index fb08e3d..b682cc4 100644 --- a/src/painter/tools/Brush.ts +++ b/src/painter/tools/Brush.ts @@ -2,6 +2,7 @@ import Painter, { Layer } from '../' import defaults from 'licia/defaults' import Tool from './Tool' import nextTick from 'licia/nextTick' +import { CursorCircle } from './Pencil' export default class Brush extends Tool { private drawCtx: CanvasRenderingContext2D @@ -9,6 +10,7 @@ export default class Brush extends Tool { private brushCavnas: HTMLCanvasElement private brushCtx: CanvasRenderingContext2D private isDrawing = false + private cursorCircle: CursorCircle private drawOptions: Required = { color: 'rgb(0,0,0)', size: 4, @@ -24,12 +26,24 @@ export default class Brush extends Tool { hardness: 100, } + this.cursorCircle = new CursorCircle( + this.cursor, + painter, + this.options.size + ) + this.drawCanvas = document.createElement('canvas') this.drawCtx = this.drawCanvas.getContext('2d')! this.brushCavnas = document.createElement('canvas') this.brushCtx = this.brushCavnas.getContext('2d')! } + setOption(name: string, val: any, renderToolbar?: boolean) { + super.setOption(name, val, renderToolbar) + if (name === 'size') { + this.cursorCircle.setSize(val) + } + } onDragStart(e: any, drawOptions: IDrawOptions = {}) { super.onDragStart(e) @@ -95,17 +109,20 @@ export default class Brush extends Tool { this.commitDraw(this.ctx) } } + onZoom() { + this.cursorCircle.render() + } private draw(x: number, y: number) { const { canvas, drawCtx } = this - const { size } = this.options + const { size } = this.drawOptions if (x < 0 || x > canvas.width || y < 0 || y > canvas.height) { return } - const centerX = size > 1 ? x - Math.floor((size - 1) / 2) : x - const centerY = size > 1 ? y - Math.floor((size - 1) / 2) : y - drawCtx.drawImage(this.brushCavnas, centerX, centerY) + const startX = size > 1 ? Math.round(x - size / 2) : x + const startY = size > 1 ? Math.round(y - size / 2) : y + drawCtx.drawImage(this.brushCavnas, startX, startY) this.painter.renderCanvas() } protected renderToolbar() { @@ -152,8 +169,8 @@ export default class Brush extends Tool { brushCtx.clearRect(0, 0, size, size) brushCtx.fillStyle = color === 'transparent' ? 'black' : color - const center = size / 2 - let radius = size / 2 + const center = Math.round(size / 2) + let radius = Math.round(size / 2) const opacityStep = 1 / radius / ((105 - hardness) / 25) let opacity = opacityStep for (; radius > 0; radius--) { diff --git a/src/painter/tools/Eraser.ts b/src/painter/tools/Eraser.ts index 4424dff..e22f485 100644 --- a/src/painter/tools/Eraser.ts +++ b/src/painter/tools/Eraser.ts @@ -2,8 +2,10 @@ import Tool from './Tool' import Brush from './Brush' import Pencil from './Pencil' import Painter, { Layer } from '../' +import { CursorCircle } from './Pencil' export default class Eraser extends Tool { + private cursorCircle: CursorCircle constructor(painter: Painter) { super(painter) @@ -13,6 +15,18 @@ export default class Eraser extends Tool { opacity: 100, hardness: 100, } + + this.cursorCircle = new CursorCircle( + this.cursor, + painter, + this.options.size + ) + } + setOption(name: string, val: any, renderToolbar?: boolean) { + super.setOption(name, val, renderToolbar) + if (name === 'size') { + this.cursorCircle.setSize(val) + } } onDragStart(e: any) { this.getTool().onDragStart(e, this.getOptions()) @@ -26,6 +40,9 @@ export default class Eraser extends Tool { onAfterRenderLayer(layer: Layer) { this.getTool().onAfterRenderLayer(layer) } + onZoom() { + this.cursorCircle.render() + } private getTool(): Brush | Pencil { return this.painter.getTool(this.options.mode) as any } diff --git a/src/painter/tools/Pencil.ts b/src/painter/tools/Pencil.ts index b2e8e4d..5d80c49 100644 --- a/src/painter/tools/Pencil.ts +++ b/src/painter/tools/Pencil.ts @@ -1,5 +1,7 @@ import Painter, { Layer } from '../' import Tool from './Tool' +import $ from 'licia/$' +import Zoom from './Zoom' import defaults from 'licia/defaults' import nextTick from 'licia/nextTick' @@ -7,6 +9,7 @@ export default class Pencil extends Tool { private drawCtx: CanvasRenderingContext2D private drawCanvas: HTMLCanvasElement private isDrawing = false + private cursorCircle: CursorCircle private drawOptions: Required = { color: 'rgb(0,0,0)', size: 1, @@ -20,9 +23,21 @@ export default class Pencil extends Tool { opacity: 100, } + this.cursorCircle = new CursorCircle( + this.cursor, + painter, + this.options.size + ) + this.drawCanvas = document.createElement('canvas') this.drawCtx = this.drawCanvas.getContext('2d')! } + setOption(name: string, val: any, renderToolbar?: boolean) { + super.setOption(name, val, renderToolbar) + if (name === 'size') { + this.cursorCircle.setSize(val) + } + } onDragStart(e: any, drawOptions: IDrawOptions = {}) { super.onDragStart(e) @@ -85,6 +100,9 @@ export default class Pencil extends Tool { this.commitDraw(this.ctx) } } + onZoom() { + this.cursorCircle.render() + } protected renderToolbar() { super.renderToolbar() const { toolbar, options } = this @@ -111,9 +129,9 @@ export default class Pencil extends Tool { const { size, color } = this.drawOptions drawCtx.fillStyle = color === 'transparent' ? 'black' : color - const centerX = size > 1 ? x - Math.floor((size - 1) / 2) : x - const centerY = size > 1 ? y - Math.floor((size - 1) / 2) : y - drawCtx.fillRect(centerX, centerY, size, size) + const startX = size > 1 ? Math.round(x - size / 2) : x + const startY = size > 1 ? Math.round(y - size / 2) : y + drawCtx.fillRect(startX, startY, size, size) this.painter.renderCanvas() } private commitDraw(ctx: CanvasRenderingContext2D) { @@ -129,6 +147,47 @@ export default class Pencil extends Tool { } } +export class CursorCircle { + private $container: $.$ + private painter: Painter + private size = 1 + constructor(container: HTMLDivElement, painter: Painter, size: number) { + this.$container = $(container) + this.painter = painter + + this.setSize(size) + } + setSize(size: number) { + this.size = size + this.render() + } + render = () => { + const { painter } = this + const zoom = painter.getTool('zoom') as Zoom + let { size } = this + if (zoom) { + size *= Math.round(zoom.getRatio()) + } + let html = '' + if (size > 1) { + const viewportSize = size + 8 + const circle = (r: number, color: string) => { + return `` + } + html = ` + ${circle(size / 2, '#000')} + ${circle(size / 2 + 1, '#fff')} + ${circle(size / 2 - 1, '#fff')} + ` + } else { + html = painter.c('') + } + this.$container.html(html) + } +} + interface IDrawOptions { color?: string size?: number diff --git a/src/painter/tools/Tool.ts b/src/painter/tools/Tool.ts index 6d3cdcd..1afc51a 100644 --- a/src/painter/tools/Tool.ts +++ b/src/painter/tools/Tool.ts @@ -1,11 +1,12 @@ import Painter, { Layer } from '../' import $ from 'licia/$' import h from 'licia/h' +import Emitter from 'licia/Emitter' import types from 'licia/types' import { eventPage } from '../../share/util' import LunaToolbar from 'luna-toolbar' -export default class Tool { +export default class Tool extends Emitter { protected painter: Painter protected x = -1 protected lastX = -1 @@ -24,6 +25,7 @@ export default class Tool { protected options: types.PlainObj = {} protected isUsing = false constructor(painter: Painter) { + super() this.painter = painter this.viewport = painter.$container @@ -43,17 +45,19 @@ export default class Tool { const toolbar = new LunaToolbar(h('div')) this.toolbar = toolbar toolbar.on('change', (key, val) => { - this.options[key] = val + this.setOption(key, val, false) }) painter.addSubComponent(toolbar) this.cursor = h(`div.${painter.c('cursor')}`) as HTMLDivElement this.$cursor = $(this.cursor) } - setOption(name: string, val: any) { + setOption(name: string, val: any, renderToolbar = true) { this.options[name] = val - this.renderToolbar() + if (renderToolbar) { + this.renderToolbar() + } } onDragStart(e: any) { this.getXY(e) @@ -82,7 +86,6 @@ export default class Tool { onMouseMove(e: any) { const { $cursor, $viewportOverlay } = this const overlayOffset = $viewportOverlay.offset() - const offset = $cursor.offset() const x = eventPage('x', e) - overlayOffset.left const y = eventPage('y', e) - overlayOffset.top @@ -93,8 +96,8 @@ export default class Tool { }) } else { $cursor.css({ - left: x - offset.width / 2, - top: y - offset.height / 2, + left: x, + top: y, opacity: 1, }) } @@ -111,6 +114,7 @@ export default class Tool { }) } onAfterRenderLayer(layer: Layer) {} + onZoom() {} protected renderToolbar() { this.toolbar.clear() } @@ -120,8 +124,8 @@ export default class Tool { const pageX = eventPage('x', e) const pageY = eventPage('y', e) - const x = Math.floor(((pageX - offset.left) / offset.width) * canvas.width) - const y = Math.floor(((pageY - offset.top) / offset.height) * canvas.height) + const x = Math.round(((pageX - offset.left) / offset.width) * canvas.width) + const y = Math.round(((pageY - offset.top) / offset.height) * canvas.height) this.lastX = this.x this.x = x diff --git a/src/painter/tools/Zoom.ts b/src/painter/tools/Zoom.ts index 1325f11..eee1130 100644 --- a/src/painter/tools/Zoom.ts +++ b/src/painter/tools/Zoom.ts @@ -11,6 +11,7 @@ interface IPivot { export default class Zoom extends Tool { private isZooming = false private isAltDown = false + private ratio = 1 constructor(painter: Painter) { super(painter) @@ -49,11 +50,15 @@ export default class Zoom extends Tool { const offset = this.$canvas.offset() this.zoomTo((offset.width * ratio) / this.canvas.width, pivot) } + getRatio() { + return this.ratio + } zoomTo(ratio: number, pivot?: IPivot) { if (this.isZooming) { return } this.isZooming = true + this.ratio = ratio const { canvas, viewport, $canvas } = this @@ -104,6 +109,7 @@ export default class Zoom extends Tool { }) viewport.scrollLeft = target.scrollLeft viewport.scrollTop = target.scrollTop + this.emit('change') }) .on('end', () => { this.isZooming = false @@ -122,8 +128,8 @@ export default class Zoom extends Tool { ) .play() } - setOption(name: string, val: any) { - super.setOption(name, val) + setOption(name: string, val: any, renderToolbar?: boolean) { + super.setOption(name, val, renderToolbar) if (name === 'mode') { const { c } = this.painter const $icon = this.$cursor.find(c('.icon'))