diff --git a/src/main.ts b/src/main.ts index c57c91e0..8c20ffb2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,7 @@ import { RotateTool } from './tools/rotate-tool'; import { ScaleTool } from './tools/scale-tool'; import { Shortcuts } from './shortcuts'; import { Events } from './events'; +import { LassoSelection } from './tools/lasso-selection'; declare global { interface LaunchParams { @@ -72,6 +73,7 @@ const initShortcuts = (events: Events) => { shortcuts.register(['F', 'f'], { event: 'camera.focus' }); shortcuts.register(['R', 'r'], { event: 'tool.rectSelection', sticky: true }); shortcuts.register(['P', 'p'], { event: 'tool.polygonSelection', sticky: true }); + shortcuts.register(['L', 'l'], { event: 'tool.lassoSelection', sticky: true }); shortcuts.register(['B', 'b'], { event: 'tool.brushSelection', sticky: true }); shortcuts.register(['A', 'a'], { event: 'select.all' }); shortcuts.register(['A', 'a'], { event: 'select.none', shift: true }); @@ -189,6 +191,7 @@ const main = async () => { toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom)); toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom, mask)); toolManager.register('polygonSelection', new PolygonSelection(events, editorUI.toolsContainer.dom, mask)); + toolManager.register('lassoSelection', new LassoSelection(events, editorUI.toolsContainer.dom, mask)); toolManager.register('sphereSelection', new SphereSelection(events, scene, editorUI.canvasContainer)); toolManager.register('move', new MoveTool(events, scene)); toolManager.register('rotate', new RotateTool(events, scene)); diff --git a/src/tools/lasso-selection.ts b/src/tools/lasso-selection.ts new file mode 100644 index 00000000..ee3a05e6 --- /dev/null +++ b/src/tools/lasso-selection.ts @@ -0,0 +1,159 @@ +import { Events } from "../events"; +import { Mask, Point, ScreenspaceSelection } from "./screenspace-selection"; + +class LassoSelection extends ScreenspaceSelection{ + private points: Point[] = []; + private currentPoint: Point = null; + private lastPointTime = 0; + private polyline: SVGPolylineElement; + + constructor(events: Events, parent: HTMLElement, mask: Mask) { + super(events, parent, mask); + + this.initSVG(); + + this.eventHandlers = { + pointerdown: this.pointerdown.bind(this), + pointermove: this.pointermove.bind(this), + pointerup: this.pointerup.bind(this) + } + } + + protected initSVG(){ + // create svg + this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement; + this.svg.id = 'lasso-select-svg'; + this.svg.classList.add('select-svg'); + + // create polyline element + this.polyline = document.createElementNS(this.svg.namespaceURI, 'polyline') as SVGPolylineElement; + this.polyline.setAttribute('fill', 'none'); + this.polyline.setAttribute('stroke-width', '1'); + this.polyline.setAttribute('stroke-dasharray', '5, 5'); + this.polyline.setAttribute('stroke-dashoffset', '0'); + + this.svg.appendChild(this.polyline); + this.parent.appendChild(this.svg); + } + + protected updateSVG() { + this.polyline.setAttribute('points', [...this.points, this.currentPoint].reduce((prev, current) => prev + `${current.x}, ${current.y} `, "")); + this.polyline.setAttribute('stroke', this.isClosed() ? '#fa6' : '#f60'); + }; + + private isClosed() { + return this.points.length > 1 && this.dist(this.currentPoint, this.points[0]) < 8; + }; + + private update(e: PointerEvent){ + this.currentPoint = {x: e.offsetX, y: e.offsetY}; + + const distance = this.points.length === 0 ? 0 : this.dist(this.currentPoint, this.points[this.points.length - 1]); + const millis = Date.now() - this.lastPointTime; + const preventCorners = distance > 20; + const slowNarrowSpacing = millis > 500 && distance > 2; + const fasterMediumSpacing = millis > 200 && distance > 10; + const firstPoints = this.points.length === 0; + + + if (this.dragId !== undefined && (preventCorners || slowNarrowSpacing || fasterMediumSpacing || firstPoints)) { + this.points.push(this.currentPoint); + this.lastPointTime = Date.now(); + this.updateSVG(); + } + }; + + private pointerdown(e: PointerEvent){ + if (this.dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) { + e.preventDefault(); + e.stopPropagation(); + + this.dragId = e.pointerId; + this.parent.setPointerCapture(this.dragId); + + // initialize canvas + if (this.mask.canvas.width !== this.parent.clientWidth || this.mask.canvas.height !== this.parent.clientHeight) { + this.mask.canvas.width = this.parent.clientWidth; + this.mask.canvas.height = this.parent.clientHeight; + } + + // clear canvas + this.mask.context.clearRect(0, 0, this.mask.canvas.width, this.mask.canvas.height); + + // display it + this.mask.canvas.style.display = 'inline'; + + this.update(e); + } + }; + + private pointermove(e: PointerEvent){ + if (this.dragId !== undefined) { + e.preventDefault(); + e.stopPropagation(); + } + + this.update(e); + }; + + protected dragEnd(){ + this.parent.releasePointerCapture(this.dragId); + this.dragId = undefined; + this.mask.canvas.style.display = 'none'; + }; + + private pointerup(e: PointerEvent) { + if (e.pointerId === this.dragId) { + e.preventDefault(); + e.stopPropagation(); + + this.dragEnd(); + + this.commitSelection(e); + + this.events.fire( + 'select.byMask', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), + this.mask.canvas, + this.mask.context + ); + } + }; + + private commitSelection(e: PointerEvent) { + // initialize canvas + if (this.mask.canvas.width !== this.parent.clientWidth || this.mask.canvas.height !== this.parent.clientHeight) { + this.mask.canvas.width = this.parent.clientWidth; + this.mask.canvas.height = this.parent.clientHeight; + } + + // clear canvas + this.mask.context.clearRect(0, 0, this.mask.canvas.width, this.mask.canvas.height); + + this.mask.context.beginPath(); + this.mask.context.fillStyle = '#f60'; + this.mask.context.beginPath(); + this.points.forEach((p, idx) => { + if (idx === 0) { + this.mask.context.moveTo(p.x, p.y); + } + else { + this.mask.context.lineTo(p.x, p.y); + } + }); + this.mask.context.closePath(); + this.mask.context.fill(); + + this.events.fire( + 'select.byMask', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), + this.mask.canvas, + this.mask.context + ); + + this.points = []; + this.updateSVG(); + }; +} + +export { LassoSelection }; diff --git a/src/tools/polygon-selection.ts b/src/tools/polygon-selection.ts index ed86c544..b3cd2aab 100644 --- a/src/tools/polygon-selection.ts +++ b/src/tools/polygon-selection.ts @@ -1,139 +1,124 @@ import { Events } from "../events"; +import { Mask, Point, ScreenspaceSelection } from "./screenspace-selection"; -type Point = { x: number, y: number }; +class PolygonSelection extends ScreenspaceSelection{ + private points: Point[] = []; + private currentPoint: Point = null; + private polyline: SVGPolylineElement; -class PolygonSelection { - activate: () => void; - deactivate: () => void; + constructor(events: Events, parent: HTMLElement, mask: Mask) { + super(events, parent, mask); - constructor(events: Events, parent: HTMLElement, mask: { canvas: HTMLCanvasElement, context: CanvasRenderingContext2D }) { - let points: Point[] = []; - let currentPoint: Point = null; + this.initSVG(); - // create svg - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - svg.id = 'polygon-select-svg'; - svg.classList.add('select-svg'); + this.eventHandlers = { + pointerdown: this.pointerdown.bind(this), + pointermove: this.pointermove.bind(this), + pointerup: this.pointerup.bind(this), + dblclick: this.dblclick.bind(this) + } + } + protected initSVG(){ + // create svg + this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement; + this.svg.id = 'polygon-select-svg'; + this.svg.classList.add('select-svg'); + // create polyline element - const polyline = document.createElementNS(svg.namespaceURI, 'polyline') as SVGPolylineElement; - polyline.setAttribute('fill', 'none'); - polyline.setAttribute('stroke-width', '1'); - polyline.setAttribute('stroke-dasharray', '5, 5'); - polyline.setAttribute('stroke-dashoffset', '0'); - - // create canvas - const { canvas, context } = mask; - - const paint = () => { - polyline.setAttribute('points', [...points, currentPoint].reduce((prev, current) => prev + `${current.x}, ${current.y} `, "")); - polyline.setAttribute('stroke', isClosed() ? '#fa6' : '#f60'); - }; + this.polyline = document.createElementNS(this.svg.namespaceURI, 'polyline') as SVGPolylineElement; + this.polyline.setAttribute('fill', 'none'); + this.polyline.setAttribute('stroke-width', '1'); + this.polyline.setAttribute('stroke-dasharray', '5, 5'); + this.polyline.setAttribute('stroke-dashoffset', '0'); + + this.svg.appendChild(this.polyline); + this.parent.appendChild(this.svg); + } - const dist = (a: Point, b: Point) => { - return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); - }; + protected updateSVG() { + this.polyline.setAttribute('points', [...this.points, this.currentPoint].reduce((prev, current) => prev + `${current.x}, ${current.y} `, "")); + this.polyline.setAttribute('stroke', this.isClosed() ? '#fa6' : '#f60'); + }; - const isClosed = () => { - return points.length > 1 && dist(currentPoint, points[0]) < 8; - }; + private isClosed() { + return this.points.length > 1 && this.dist(this.currentPoint, this.points[0]) < 8; + }; - const pointermove = (e: PointerEvent) => { - currentPoint = { x: e.offsetX, y: e.offsetY }; + private pointermove(e: PointerEvent){ + this.currentPoint = { x: e.offsetX, y: e.offsetY }; - if (points.length > 0) { - paint(); - } - }; + if (this.points.length > 0) { + this.updateSVG(); + } + }; - const pointerdown = (e: PointerEvent) => { - if (points.length > 0 || (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) { - e.preventDefault(); - e.stopPropagation(); - } - }; - - const pointerup = (e: PointerEvent) => { - if (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary) { - e.preventDefault(); - e.stopPropagation(); - - if (isClosed()) { - commitSelection(e); - } else if (points.length === 0 || dist(points[points.length - 1], currentPoint) > 0) { - points.push(currentPoint); - } - } - }; + private pointerdown(e: PointerEvent){ + if (this.points.length > 0 || (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) { + e.preventDefault(); + e.stopPropagation(); + } + }; - const dblclick = (e: PointerEvent) => { + private pointerup(e: PointerEvent){ + if (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary) { e.preventDefault(); e.stopPropagation(); - if (points.length > 2) { - commitSelection(e); + if (this.isClosed()) { + this.commitSelection(e); + } else if (this.points.length === 0 || this.dist(this.points[this.points.length - 1], this.currentPoint) > 0) { + this.points.push(this.currentPoint); } - }; - - const commitSelection = (e: PointerEvent) => { - // initialize canvas - if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) { - canvas.width = parent.clientWidth; - canvas.height = parent.clientHeight; + } + }; + + private dblclick(e: PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + + if (this.points.length > 2) { + this.commitSelection(e); + } + }; + + private commitSelection(e: PointerEvent) { + // initialize canvas + if (this.mask.canvas.width !== this.parent.clientWidth || this.mask.canvas.height !== this.parent.clientHeight) { + this.mask.canvas.width = this.parent.clientWidth; + this.mask.canvas.height = this.parent.clientHeight; + } + + // clear canvas + this.mask.context.clearRect(0, 0, this.mask.canvas.width, this.mask.canvas.height); + + this.mask.context.beginPath(); + this.mask.context.fillStyle = '#f60'; + this.mask.context.beginPath(); + this.points.forEach((p, idx) => { + if (idx === 0) { + this.mask.context.moveTo(p.x, p.y); } - - // clear canvas - context.clearRect(0, 0, canvas.width, canvas.height); - - context.beginPath(); - context.fillStyle = '#f60'; - context.beginPath(); - points.forEach((p, idx) => { - if (idx === 0) { - context.moveTo(p.x, p.y); - } - else { - context.lineTo(p.x, p.y); - } - }); - context.closePath(); - context.fill(); - - events.fire( - 'select.byMask', - e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), - canvas, - context - ); - - points = []; - paint(); - }; - - this.activate = () => { - svg.style.display = 'inline'; - parent.style.display = 'block'; - parent.addEventListener('pointerdown', pointerdown); - parent.addEventListener('pointermove', pointermove); - parent.addEventListener('pointerup', pointerup); - parent.addEventListener('dblclick', dblclick); - }; - - this.deactivate = () => { - // cancel active operation - svg.style.display = 'none'; - parent.style.display = 'none'; - parent.removeEventListener('pointerdown', pointerdown); - parent.removeEventListener('pointermove', pointermove); - parent.removeEventListener('pointerup', pointerup); - parent.removeEventListener('dblclick', dblclick); - points = []; - paint(); - }; - - svg.appendChild(polyline); - parent.appendChild(svg); - } + else { + this.mask.context.lineTo(p.x, p.y); + } + }); + this.mask.context.closePath(); + this.mask.context.fill(); + + this.events.fire( + 'select.byMask', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), + this.mask.canvas, + this.mask.context + ); + + this.points = []; + this.updateSVG(); + }; + + protected dragEnd(){ + }; } export { PolygonSelection }; diff --git a/src/tools/rect-selection.ts b/src/tools/rect-selection.ts index e3e0f32e..2300ea79 100644 --- a/src/tools/rect-selection.ts +++ b/src/tools/rect-selection.ts @@ -1,129 +1,118 @@ import { Events } from '../events'; +import { Point, ScreenspaceSelection } from './screenspace-selection'; -class RectSelection { - - activate: () => void; - deactivate: () => void; +class RectSelection extends ScreenspaceSelection{ + private start: Point = { x: 0, y: 0 }; + private end: Point = { x: 0, y: 0 }; + private dragMoved = false; + private rect: SVGRectElement; constructor(events: Events, parent: HTMLElement) { - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - svg.id = 'rect-select-svg'; - svg.classList.add('select-svg'); - - // create rect element - const rect = document.createElementNS(svg.namespaceURI, 'rect') as SVGRectElement; - rect.setAttribute('fill', 'none'); - rect.setAttribute('stroke', '#f60'); - rect.setAttribute('stroke-width', '1'); - rect.setAttribute('stroke-dasharray', '5, 5'); - - const start = { x: 0, y: 0 }; - const end = { x: 0, y: 0 }; - let dragId: number | undefined; - let dragMoved = false; - - const updateRect = () => { - const x = Math.min(start.x, end.x); - const y = Math.min(start.y, end.y); - const width = Math.abs(start.x - end.x); - const height = Math.abs(start.y - end.y); - - rect.setAttribute('x', x.toString()); - rect.setAttribute('y', y.toString()); - rect.setAttribute('width', width.toString()); - rect.setAttribute('height', height.toString()); - }; - - const pointerdown = (e: PointerEvent) => { - if (dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) { - e.preventDefault(); - e.stopPropagation(); - - dragId = e.pointerId; - dragMoved = false; - parent.setPointerCapture(dragId); - - start.x = end.x = e.offsetX; - start.y = end.y = e.offsetY; - - updateRect(); - - svg.style.display = 'inline'; - } - }; - - const pointermove = (e: PointerEvent) => { - if (e.pointerId === dragId) { - e.preventDefault(); - e.stopPropagation(); + super(events, parent); - dragMoved = true; - end.x = e.offsetX; - end.y = e.offsetY; + this.initSVG(); - updateRect(); - } - }; - - const dragEnd = () => { - parent.releasePointerCapture(dragId); - dragId = undefined; - svg.style.display = 'none'; - }; - - const pointerup = (e: PointerEvent) => { - if (e.pointerId === dragId) { - e.preventDefault(); - e.stopPropagation(); - - const w = parent.clientWidth; - const h = parent.clientHeight; - - dragEnd(); - - if (dragMoved) { - // rect select - events.fire( - 'select.rect', - e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), { - start: { x: Math.min(start.x, end.x) / w, y: Math.min(start.y, end.y) / h }, - end: { x: Math.max(start.x, end.x) / w, y: Math.max(start.y, end.y) / h }, - }); - } else { - // pick - events.fire( - 'select.point', - e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), - { x: e.offsetX / parent.clientWidth, y: e.offsetY / parent.clientHeight } - ); - } - } - }; - - this.activate = () => { - parent.style.display = 'block'; - parent.addEventListener('pointerdown', pointerdown); - parent.addEventListener('pointermove', pointermove); - parent.addEventListener('pointerup', pointerup); - }; - - this.deactivate = () => { - if (dragId !== undefined) { - dragEnd(); - } - parent.style.display = 'none'; - parent.removeEventListener('pointerdown', pointerdown); - parent.removeEventListener('pointermove', pointermove); - parent.removeEventListener('pointerup', pointerup); - }; - - parent.appendChild(svg); - svg.appendChild(rect); + this.eventHandlers = { + pointerdown: this.pointerdown.bind(this), + pointermove: this.pointermove.bind(this), + pointerup: this.pointerup.bind(this) + } } - destroy() { + protected initSVG(){ + // create svg + this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement; + this.svg.id = 'rect-select-svg'; + this.svg.classList.add('select-svg'); + // create rect element + this.rect = document.createElementNS(this.svg.namespaceURI, 'rect') as SVGRectElement; + this.rect.setAttribute('fill', 'none'); + this.rect.setAttribute('stroke', '#f60'); + this.rect.setAttribute('stroke-width', '1'); + this.rect.setAttribute('stroke-dasharray', '5, 5'); + + this.svg.appendChild(this.rect); + this.parent.appendChild(this.svg); } + + protected updateSVG() { + const x = Math.min(this.start.x, this.end.x); + const y = Math.min(this.start.y, this.end.y); + const width = Math.abs(this.start.x - this.end.x); + const height = Math.abs(this.start.y - this.end.y); + + this.rect.setAttribute('x', x.toString()); + this.rect.setAttribute('y', y.toString()); + this.rect.setAttribute('width', width.toString()); + this.rect.setAttribute('height', height.toString()); + }; + + private pointerdown(e: PointerEvent){ + if (this.dragId === undefined && (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) { + e.preventDefault(); + e.stopPropagation(); + + this.dragId = e.pointerId; + this.dragMoved = false; + this.parent.setPointerCapture(this.dragId); + + this.start.x = this.end.x = e.offsetX; + this.start.y = this.end.y = e.offsetY; + + this.updateSVG(); + + this.svg.style.display = 'inline'; + } + }; + + private pointermove(e: PointerEvent) { + if (e.pointerId === this.dragId) { + e.preventDefault(); + e.stopPropagation(); + + this.dragMoved = true; + this.end.x = e.offsetX; + this.end.y = e.offsetY; + + this.updateSVG(); + } + }; + + private pointerup(e: PointerEvent){ + if (e.pointerId === this.dragId) { + e.preventDefault(); + e.stopPropagation(); + + const w = this.parent.clientWidth; + const h = this.parent.clientHeight; + + this.dragEnd(); + + if (this.dragMoved) { + // rect select + this.events.fire( + 'select.rect', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), { + start: { x: Math.min(this.start.x, this.end.x) / w, y: Math.min(this.start.y, this.end.y) / h }, + end: { x: Math.max(this.start.x, this.end.x) / w, y: Math.max(this.start.y, this. end.y) / h }, + }); + } else { + // pick + this.events.fire( + 'select.point', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), + { x: e.offsetX / this.parent.clientWidth, y: e.offsetY / this.parent.clientHeight } + ); + } + } + }; + + protected dragEnd() { + this.parent.releasePointerCapture(this.dragId); + this.dragId = undefined; + this.svg.style.display = 'none'; + }; } export { RectSelection }; diff --git a/src/tools/screenspace-selection.ts b/src/tools/screenspace-selection.ts new file mode 100644 index 00000000..d6bd11b1 --- /dev/null +++ b/src/tools/screenspace-selection.ts @@ -0,0 +1,46 @@ +import { Events } from "../events"; + +type Point = { x: number, y: number }; +type EventHandlerRegistry = { [key: string] : (e: PointerEvent) => void}; +type Mask = { canvas: HTMLCanvasElement, context: CanvasRenderingContext2D }; + +abstract class ScreenspaceSelection { + protected mask?: Mask; + protected parent: HTMLElement; + protected events: Events; + protected svg: SVGSVGElement; + protected eventHandlers: EventHandlerRegistry; + protected dragId?: number; + + constructor(events: Events, parent: HTMLElement, mask?: Mask) { + this.events = events; + this.parent = parent; + this.mask = mask; + } + + protected abstract initSVG(): void; + protected abstract updateSVG(): void; + protected abstract dragEnd(): void; + + protected dist(a: Point, b: Point){ + return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); + }; + + public activate() { + this.svg.style.display = 'inline'; + this.parent.style.display = 'block'; + Object.entries(this.eventHandlers).forEach(([name, handler]) => this.parent.addEventListener(name, handler)); + }; + + public deactivate() { + // cancel active operation + if (this.dragId !== undefined) { + this.dragEnd(); + } + this.svg.style.display = 'none'; + this.parent.style.display = 'none'; + Object.entries(this.eventHandlers).forEach(([name, handler]) => this.parent.removeEventListener(name, handler)); + }; +} + +export { ScreenspaceSelection, Point, Mask, EventHandlerRegistry }; diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 70b2dd1a..c6ceddf6 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -9,7 +9,7 @@ import pickerSvg from './svg/select-picker.svg'; import brushSvg from './svg/select-brush.svg'; import polygonSvg from './svg/select-poly.svg'; import sphereSvg from './svg/select-sphere.svg'; -// import lassoSvg from './svg/select-lasso.svg'; +import lassoSvg from './svg/select-lasso.svg'; // import cropSvg from './svg/crop.svg'; const createSvg = (svgString: string) => { @@ -57,10 +57,10 @@ class BottomToolbar extends Container { class: 'bottom-toolbar-tool' }); - // const lasso = new Button({ - // id: 'bottom-toolbar-lasso', - // class: ['bottom-toolbar-tool', 'disabled'] - // }); + const lasso = new Button({ + id: 'bottom-toolbar-lasso', + class: 'bottom-toolbar-tool' + }); const sphere = new Button({ id: 'bottom-toolbar-sphere', @@ -102,16 +102,16 @@ class BottomToolbar extends Container { polygon.dom.appendChild(createSvg(polygonSvg)); brush.dom.appendChild(createSvg(brushSvg)); sphere.dom.appendChild(createSvg(sphereSvg)); - // lasso.dom.appendChild(createSvg(lassoSvg)); + lasso.dom.appendChild(createSvg(lassoSvg)); // crop.dom.appendChild(createSvg(cropSvg)); this.append(undo); this.append(redo); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(picker); + this.append(lasso); this.append(polygon); this.append(brush); - // this.append(lasso); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(sphere); // this.append(crop); @@ -124,6 +124,7 @@ class BottomToolbar extends Container { undo.dom.addEventListener('click', () => events.fire('edit.undo')); redo.dom.addEventListener('click', () => events.fire('edit.redo')); polygon.dom.addEventListener('click', () => events.fire('tool.polygonSelection')); + lasso.dom.addEventListener('click', () => events.fire('tool.lassoSelection')); brush.dom.addEventListener('click', () => events.fire('tool.brushSelection')); picker.dom.addEventListener('click', () => events.fire('tool.rectSelection')); sphere.dom.addEventListener('click', () => events.fire('tool.sphereSelection')); @@ -139,6 +140,7 @@ class BottomToolbar extends Container { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); polygon.class[toolName === 'polygonSelection' ? 'add' : 'remove']('active'); + lasso.class[toolName === 'lassoSelection' ? 'add' : 'remove']('active'); sphere.class[toolName === 'sphereSelection' ? 'add' : 'remove']('active'); translate.class[toolName === 'move' ? 'add' : 'remove']('active'); rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); @@ -155,7 +157,7 @@ class BottomToolbar extends Container { tooltips.register(picker, localize('tooltip.picker')); tooltips.register(brush, localize('tooltip.brush')); tooltips.register(polygon, localize('tooltip.polygon')); - // tooltips.register(lasso, 'Lasso Select'); + tooltips.register(lasso, 'Lasso Select'); tooltips.register(sphere, localize('tooltip.sphere')); // tooltips.register(crop, 'Crop'); tooltips.register(translate, localize('tooltip.translate'));