Removed the stylus folder and added stylus.js with doctest to lib/pixi

This commit is contained in:
Uwe Oestermeier 2019-07-04 09:09:19 +02:00
parent da212ac188
commit af932a9911
4 changed files with 1048 additions and 51 deletions

424
dist/iwmlib.pixi.js vendored
View File

@ -1402,6 +1402,8 @@
Events$1.simulated = []; Events$1.simulated = [];
Events$1.simulationRunning = false; Events$1.simulationRunning = false;
/* global PIXI TweenLite */
/** /**
* Callback for the button action. * Callback for the button action.
* *
@ -1643,6 +1645,7 @@
TweenLite.to([this.button, this.content], this.theme.fast, { alpha: 1, overwrite: 'none' }); TweenLite.to([this.button, this.content], this.theme.fast, { alpha: 1, overwrite: 'none' });
}); });
// eslint-disable-next-line no-unused-vars
this.button.on('pointerdown', e => { this.button.on('pointerdown', e => {
//this.capture(e) //this.capture(e)
TweenLite.to([this.button, this.content], this.theme.fast, { alpha: .7, overwrite: 'none' }); TweenLite.to([this.button, this.content], this.theme.fast, { alpha: .7, overwrite: 'none' });
@ -13819,6 +13822,426 @@
} }
} }
/* eslint-disable no-undef */
class StylusCommand extends Object {
constructor() {
super();
}
do(stylus) {
stylus.commandStack.push(this);
}
undo(stylus) {
stylus.undoCommandStack.push(this);
}
redo(stylus) {
this.do(stylus);
}
}
class StrokeCommand extends StylusCommand {
constructor(stroke) {
super();
this.stroke = stroke;
}
do(stylus) {
if (this.stroke.length > 0) {
super.do(stylus);
stylus.stroke = [];
stylus.strokes.push(this.stroke);
stylus.redraw();
stylus.changed();
}
}
undo(stylus) {
if (this.stroke.length > 0) {
super.undo(stylus);
stylus.strokes.pop();
stylus.redraw();
stylus.changed();
}
}
}
class ClearCommand extends StylusCommand {
do(stylus) {
// Clears the command stack
stylus.commandStack = [];
super.do(stylus);
this.strokes = stylus.strokes;
stylus.stroke = [];
stylus.strokes = [];
stylus.redraw();
stylus.changed();
}
undo(stylus) {
//super.undo(stylus) // Clear all is not redoable
stylus.stroke = [];
stylus.strokes = this.strokes;
stylus.redraw();
stylus.changed();
}
}
class Stylus extends PIXI.Graphics {
constructor({ width = window.innerWidth,
height = window.innerHeight,
interactive = true,
color = 0x000000,
tiltX = 0,
tiltY = 0,
backgroundAlpha = 1,
backgroundFill = 0xFFFFFF,
colorAlpha = 1,
captureEvents = true,
acceptMouseEvents = true } = {}) {
super();
this.activePointers = 0;
this.wantedWidth = width;
this.wantedHeight = height;
this.backgroundAlpha = backgroundAlpha;
this.backgroundFill = backgroundFill;
this.colorAlpha = colorAlpha;
this.color = color;
this.interactive = interactive;
this.debug = false;
this.tiltX = tiltX; // degrees -90 ... 90
this.tiltY = tiltY; // degrees -90 ... 90
this.captureEvents = captureEvents;
this.commandStack = [];
this.undoCommandStack = [];
this.strokes = [];
this.stroke = [];
this.minStrokeLength = 4;
if (captureEvents)
this.registerEventHandler(acceptMouseEvents);
this.drawBackground();
}
drawBackground() {
this.clear();
this.beginFill(this.backgroundFill, this.backgroundAlpha);
this.drawRect(0, 0, this.wantedWidth, this.wantedHeight);
this.endFill();
}
touchToPoint(t) {
return { x: t.clientX, y: t.clientY }
}
isStylusPointer(event) {
let identifier = event.data.identifier;
if (typeof (event.data.originalEvent.changedTouches) !== 'undefined') {
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier && touch.touchType === 'stylus') {
this.tiltX = Angle.radian2degree(touch.azimuthAngle);
this.tiltY = 90.0 - Angle.radian2degree(touch.altitudeAngle);
return true
}
}
}
// UO: Not tested since the Sprot delivered "mouse" events to Chrome
if (event.data.originalEvent.pointerType === 'pen') {
this.tiltX = event.data.originalEvent.tiltX;
this.tiltY = event.data.originalEvent.tiltY;
return true
}
return false
}
isStylusTouch(event) {
let identifier = event.data.identifier;
if (typeof (event.data.originalEvent.changedTouches) !== 'undefined') {
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier && touch.pointerType === 'touch') {
return true
}
}
}
return false
}
getPointerID(event) {
let identifier = event.data.identifier;
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier) {
return touch.pointerId
}
}
}
singlePointer() {
return this.activePointers == 1
}
registerEventHandler() {
window.addEventListener('keydown', (e) => {
switch (e.keyCode) {
case 38: // up arrow
this.tiltX += 5;
break
case 40: // down arrow
this.tiltX -= 5;
break
case 37: // left arrow
this.tiltY -= 5;
break
case 39: // right arrow
this.tiltY += 5;
break
}
if (this.debug) console.log('keydown', e.keyCode, this.tiltX, this.tiltY);
});
this.on('pointerdown', (e) => {
if (this.debug) console.log('pointerdown', e);
if (this.eventInside(e)) {
this.activePointers += 1;
if (this.singlePointer()) {
this.startStroke(this.toStroke(e));
}
}
});
this.on('pointermove', (e) => {
if (Events$1.isPointerDown(e.data.originalEvent) || this.isStylusPointer(e) || this.isStylusTouch(e)) {
if (this.debug) console.log('pointermove', e, this.eventInside(e));
if (this.eventInside(e) && this.singlePointer())
this.moveStroke(this.toStroke(e));
}
});
this.on('pointerup', (e) => {
if (this.eventInside(e)) {
if (this.activePointers > 0) {
this.activePointers -= 1;
this.endStroke(this.toStroke(e));
}
}
if (this.debug) console.log('pointerup', this.activePointers);
});
this.on('pointerleave', (e) => {
if (this.activePointers > 0) {
this.activePointers -= 1;
}
this.endStroke(this.toStroke(e));
});
this.on('pointercancel', (e) => {
if (this.activePointers > 0) {
this.activePointers -= 1;
}
this.endStroke(this.toStroke(e));
});
}
undoable() {
return this.commandStack.length > 0
}
redoable() {
return this.undoCommandStack.length > 0
}
undo() {
if (this.undoable()) {
let cmd = this.commandStack.pop();
cmd.undo(this);
}
}
redo() {
if (this.redoable()) {
let cmd = this.undoCommandStack.pop();
cmd.redo(this);
}
}
eventInside(event) {
let local = this.toLocal(event.data.global);
for (let child of this.children) {
let r = child.getBounds();
if (r.contains(local.x, local.y)) {
console.log('Child touched');
return false
}
}
if (local.x < 0 || local.x > this.wantedWidth)
return false
if (local.y < 0 || local.y > this.wantedHeight)
return false
event.stopPropagation();
// if (this.debug) console.log('stopPropagation', event)
if (event.data.originalEvent.claimedByScatter) {
return false
}
return true
}
toLocalPoint(event) {
return this.toLocal(event.data.global)
}
toStroke(event) {
let local = this.toLocalPoint(event);
let x = Math.max(0, Math.min(local.x, this.wantedWidth));
let y = Math.max(0, Math.min(local.y, this.wantedHeight));
let desc = {
x, y,
pressure: event.pressure || null,
tiltX: this.tiltX, tiltY: this.tiltY,
color: this.color
};
return desc
}
startStroke(info) {
this.stroke = [info];
this.redraw();
}
moveStroke(info) {
this.stroke.push(info);
this.redraw();
}
// eslint-disable-next-line no-unused-vars
endStroke(info) {
if (this.stroke.length >= this.minStrokeLength) {
let cmd = new StrokeCommand(this.stroke);
cmd.do(this);
}
}
tiltToLineWidth(value) {
return Math.round(Math.abs(value / 10) + 1)
}
drawStroke(stroke) {
if (stroke.length) {
let start = stroke[0];
this.beginFill(0, 0);
this.moveTo(start.x, start.y);
for (let i = 1; i < stroke.length; i++) {
let info = stroke[i];
this.lineStyle(this.tiltToLineWidth(info.tiltY),
info.color, this.colorAlpha);
this.lineTo(info.x, info.y);
}
this.endFill();
}
}
drawTouch(point) {
this.beginFill(0, 0);
this.drawCircle(point.x, point.y, 22);
this.endFill();
}
drawStrokes() {
this.drawBackground();
this.lineStyle(1.0, 0xFF0000, 1);
for (let stroke of this.iterStrokes()) {
this.drawStroke(stroke);
}
}
redraw() {
this.drawStrokes();
}
// Can be overwritten if different levels of strokes are necessary
*iterStrokes() {
for (let stroke of this.strokes) {
yield stroke;
}
yield this.stroke;
}
changed() {
// Can be overwritten
}
clearAll() {
let cmd = new ClearCommand();
cmd.do(this);
}
normalizeInfo(info) {
let { x, y, pressure, tiltX, tiltY, color } = info;
x /= this.wantedWidth;
y /= this.wantedHeight;
return { x, y, pressure, tiltX, tiltY, color }
}
denormalizeInfo(info) {
let { x, y, pressure, tiltX, tiltY, color } = info;
x = x * this.wantedWidth;
y = y * this.wantedHeight;
return { x, y, pressure, tiltX, tiltY, color }
}
// Convert strokes into an object that can be stored in an Indexed DB.
// Returns normalized strokes
toObject() {
let result = [];
for (let stroke of this.strokes) {
let normalized = [];
for (let info of stroke) {
normalized.push(this.normalizeInfo(info));
}
result.push(normalized);
}
return result
}
// Read normalized strokes from an object from an Indexed DB.
fromObject(normalizedStrokes) {
this.strokes = [];
for (let stroke of normalizedStrokes) {
let denormalized = [];
for (let info of stroke) {
denormalized.push(this.denormalizeInfo(info));
}
this.strokes.push(denormalized);
}
}
// Convert strokes into a JSON object that can be stored in an Indexed DB
toJSON() {
return JSON.stringify(this.toObject())
}
// Convert strokes from a JSON
fromJSON(json) {
this.fromObject(JSON.parse(json));
}
// Returns a set of used colors
usedColors() {
let used = new Set();
for (let info of this.stroke) {
used.add(info.color);
}
for (let stroke of this.strokes) {
for (let info of stroke) {
used.add(info.color);
}
}
return used.values()
}
}
/** /**
* Callback for the switch action. * Callback for the switch action.
* *
@ -14997,6 +15420,7 @@
window.ButtonGroup = ButtonGroup; window.ButtonGroup = ButtonGroup;
window.Scrollview = Scrollview; window.Scrollview = Scrollview;
window.Slider = Slider; window.Slider = Slider;
window.Stylus = Stylus;
window.Switch = Switch; window.Switch = Switch;
window.Popup = Popup; window.Popup = Popup;
window.PopupMenu = PopupMenu$1; window.PopupMenu = PopupMenu$1;

View File

@ -12,6 +12,7 @@ import Button from './button.js'
import ButtonGroup from './buttongroup.js' import ButtonGroup from './buttongroup.js'
import Scrollview from './scrollview.js' import Scrollview from './scrollview.js'
import Slider from './slider.js' import Slider from './slider.js'
import Stylus from './stylus.js'
import Switch from './switch.js' import Switch from './switch.js'
import Popup from './popup.js' import Popup from './popup.js'
import PopupMenu from './popupmenu.js' import PopupMenu from './popupmenu.js'
@ -44,6 +45,7 @@ window.Button = Button
window.ButtonGroup = ButtonGroup window.ButtonGroup = ButtonGroup
window.Scrollview = Scrollview window.Scrollview = Scrollview
window.Slider = Slider window.Slider = Slider
window.Stylus = Stylus
window.Switch = Switch window.Switch = Switch
window.Popup = Popup window.Popup = Popup
window.PopupMenu = PopupMenu window.PopupMenu = PopupMenu

149
lib/pixi/stylus.html Normal file
View File

@ -0,0 +1,149 @@
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>PIXI Stylus</title>
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../css/doctest.css">
<script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>Stylus</h1>
<p>The Stylus class extends the PIXI.Graphics class and allows to draw into
a graphics object. Select the pen tool in the following example app and draw into the canvas.
</p>
<canvas id="canvas" style="border: 1px solid gray; width: 640px; height: 480px;" class="interactive"></canvas>
<p>
What you should see: A blank sytlus canvas.
</p>
<script class="doctest">
class StylusApp extends PIXIApp {
sceneFactory() {
return new Stylus(this.renderer)
}
setup() {
let buttonColor = 0x666666
super.setup()
this.tools = new ButtonGroup({
type: 'checkbox',
margin: 0,
x: 16,
y: 16,
fill: buttonColor,
buttons: [{
icon: 'edit',
iconColorActive: 0xFFFF00,
action: (event, button) => this.toggleEditMode()
},
{
icon: 'undo',
action: (event, button) => this.undo(button)
},
{
icon: 'redo',
action: (event, button) => this.redo(button)
},
{
icon: 'delete',
action: (event, button) => this.clear(button)
}
]
})
this.scene.addChild(this.tools)
let defaults = {
icon: 'brightness_1',
action: (event, button) => this.selectColor(button),
fillAlpha: 0,
strokeAlpha: 0,
fillActiveAlpha: 0,
strokeActiveAlpha: 0
}
this.palette = new ButtonGroup({
type: "radio",
x: 200,
y: 16,
margin: 0,
strokeAlpha: 0,
fill: buttonColor,
buttons: [
Object.assign({}, defaults, {
iconColor: 0x111111,
iconColorActive: 0x111111
}), // tooltip: "Black",
Object.assign({}, defaults, {
iconColor: 0xFFFF00,
iconColorActive: 0xFFFF00
}), // tooltip: "Yellow",
Object.assign({}, defaults, {
iconColor: 0x00FF00,
iconColorActive: 0x00FF00
}), // tooltip: "Green",
Object.assign({}, defaults, {
iconColor: 0xFF00FF,
iconColorActive: 0xFF00FF
}) // tooltip: "Violet",
]
})
this.scene.addChild(this.palette)
this.scene.interactive = false
}
toggleEditMode() {
this.scene.interactive = !this.scene.interactive
console.log("this.scene.interactive")
}
selectColor(button) {
this.scene.color = button.opts.iconColor
}
undo(button) {
this.scene.undo()
setTimeout(() => {
button.active = false
}, 200)
}
redo(button) {
this.scene.redo()
setTimeout(() => {
button.active = false
}, 200)
}
clear(button) {
this.scene.clearAll()
setTimeout(() => {
button.active = false
}, 200)
}
}
const app = new StylusApp({
view: canvas,
width: 640,
height: 480,
autoResize: false
})
window.app = app
app.setup()
app.run()
</script>
</body>

422
lib/pixi/stylus.js Executable file
View File

@ -0,0 +1,422 @@
/* eslint-disable no-undef */
/* eslint-disable no-console */
import Events from '../events.js'
import { Angle } from '../utils.js'
class StylusCommand extends Object {
constructor() {
super()
}
do(stylus) {
stylus.commandStack.push(this)
}
undo(stylus) {
stylus.undoCommandStack.push(this)
}
redo(stylus) {
this.do(stylus)
}
}
class StrokeCommand extends StylusCommand {
constructor(stroke) {
super()
this.stroke = stroke
}
do(stylus) {
if (this.stroke.length > 0) {
super.do(stylus)
stylus.stroke = []
stylus.strokes.push(this.stroke)
stylus.redraw()
stylus.changed()
}
}
undo(stylus) {
if (this.stroke.length > 0) {
super.undo(stylus)
stylus.strokes.pop()
stylus.redraw()
stylus.changed()
}
}
}
class ClearCommand extends StylusCommand {
do(stylus) {
// Clears the command stack
stylus.commandStack = []
super.do(stylus)
this.strokes = stylus.strokes
stylus.stroke = []
stylus.strokes = []
stylus.redraw()
stylus.changed()
}
undo(stylus) {
//super.undo(stylus) // Clear all is not redoable
stylus.stroke = []
stylus.strokes = this.strokes
stylus.redraw()
stylus.changed()
}
}
export default class Stylus extends PIXI.Graphics {
constructor({ width = window.innerWidth,
height = window.innerHeight,
interactive = true,
color = 0x000000,
tiltX = 0,
tiltY = 0,
backgroundAlpha = 1,
backgroundFill = 0xFFFFFF,
colorAlpha = 1,
captureEvents = true,
acceptMouseEvents = true } = {}) {
super()
this.activePointers = 0
this.wantedWidth = width
this.wantedHeight = height
this.backgroundAlpha = backgroundAlpha
this.backgroundFill = backgroundFill
this.colorAlpha = colorAlpha
this.color = color
this.interactive = interactive
this.debug = false
this.tiltX = tiltX // degrees -90 ... 90
this.tiltY = tiltY // degrees -90 ... 90
this.captureEvents = captureEvents
this.commandStack = []
this.undoCommandStack = []
this.strokes = []
this.stroke = []
this.minStrokeLength = 4
if (captureEvents)
this.registerEventHandler(acceptMouseEvents)
this.drawBackground()
}
drawBackground() {
this.clear()
this.beginFill(this.backgroundFill, this.backgroundAlpha)
this.drawRect(0, 0, this.wantedWidth, this.wantedHeight)
this.endFill()
}
touchToPoint(t) {
return { x: t.clientX, y: t.clientY }
}
isStylusPointer(event) {
let identifier = event.data.identifier
if (typeof (event.data.originalEvent.changedTouches) !== 'undefined') {
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier && touch.touchType === 'stylus') {
this.tiltX = Angle.radian2degree(touch.azimuthAngle)
this.tiltY = 90.0 - Angle.radian2degree(touch.altitudeAngle)
return true
}
}
}
// UO: Not tested since the Sprot delivered "mouse" events to Chrome
if (event.data.originalEvent.pointerType === 'pen') {
this.tiltX = event.data.originalEvent.tiltX
this.tiltY = event.data.originalEvent.tiltY
return true
}
return false
}
isStylusTouch(event) {
let identifier = event.data.identifier
if (typeof (event.data.originalEvent.changedTouches) !== 'undefined') {
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier && touch.pointerType === 'touch') {
return true
}
}
}
return false
}
getPointerID(event) {
let identifier = event.data.identifier
for (let touch of event.data.originalEvent.changedTouches) {
if (touch.identifier === identifier) {
return touch.pointerId
}
}
}
singlePointer() {
return this.activePointers == 1
}
registerEventHandler() {
window.addEventListener('keydown', (e) => {
switch (e.keyCode) {
case 38: // up arrow
this.tiltX += 5
break
case 40: // down arrow
this.tiltX -= 5
break
case 37: // left arrow
this.tiltY -= 5
break
case 39: // right arrow
this.tiltY += 5
break
}
if (this.debug) console.log('keydown', e.keyCode, this.tiltX, this.tiltY)
})
this.on('pointerdown', (e) => {
if (this.debug) console.log('pointerdown', e)
if (this.eventInside(e)) {
this.activePointers += 1
if (this.singlePointer()) {
this.startStroke(this.toStroke(e))
}
}
})
this.on('pointermove', (e) => {
if (Events.isPointerDown(e.data.originalEvent) || this.isStylusPointer(e) || this.isStylusTouch(e)) {
if (this.debug) console.log('pointermove', e, this.eventInside(e))
if (this.eventInside(e) && this.singlePointer())
this.moveStroke(this.toStroke(e))
}
})
this.on('pointerup', (e) => {
if (this.eventInside(e)) {
if (this.activePointers > 0) {
this.activePointers -= 1
this.endStroke(this.toStroke(e))
}
}
if (this.debug) console.log('pointerup', this.activePointers)
})
this.on('pointerleave', (e) => {
if (this.activePointers > 0) {
this.activePointers -= 1
}
this.endStroke(this.toStroke(e))
})
this.on('pointercancel', (e) => {
if (this.activePointers > 0) {
this.activePointers -= 1
}
this.endStroke(this.toStroke(e))
})
}
undoable() {
return this.commandStack.length > 0
}
redoable() {
return this.undoCommandStack.length > 0
}
undo() {
if (this.undoable()) {
let cmd = this.commandStack.pop()
cmd.undo(this)
}
}
redo() {
if (this.redoable()) {
let cmd = this.undoCommandStack.pop()
cmd.redo(this)
}
}
eventInside(event) {
let local = this.toLocal(event.data.global)
for (let child of this.children) {
let r = child.getBounds()
if (r.contains(local.x, local.y)) {
console.log('Child touched')
return false
}
}
if (local.x < 0 || local.x > this.wantedWidth)
return false
if (local.y < 0 || local.y > this.wantedHeight)
return false
event.stopPropagation()
// if (this.debug) console.log('stopPropagation', event)
if (event.data.originalEvent.claimedByScatter) {
return false
}
return true
}
toLocalPoint(event) {
return this.toLocal(event.data.global)
}
toStroke(event) {
let local = this.toLocalPoint(event)
let x = Math.max(0, Math.min(local.x, this.wantedWidth))
let y = Math.max(0, Math.min(local.y, this.wantedHeight))
let desc = {
x, y,
pressure: event.pressure || null,
tiltX: this.tiltX, tiltY: this.tiltY,
color: this.color
}
return desc
}
startStroke(info) {
this.stroke = [info]
this.redraw()
}
moveStroke(info) {
this.stroke.push(info)
this.redraw()
}
// eslint-disable-next-line no-unused-vars
endStroke(info) {
if (this.stroke.length >= this.minStrokeLength) {
let cmd = new StrokeCommand(this.stroke)
cmd.do(this)
}
}
tiltToLineWidth(value) {
return Math.round(Math.abs(value / 10) + 1)
}
drawStroke(stroke) {
if (stroke.length) {
let start = stroke[0]
this.beginFill(0, 0)
this.moveTo(start.x, start.y)
for (let i = 1; i < stroke.length; i++) {
let info = stroke[i]
this.lineStyle(this.tiltToLineWidth(info.tiltY),
info.color, this.colorAlpha)
this.lineTo(info.x, info.y)
}
this.endFill()
}
}
drawTouch(point) {
this.beginFill(0, 0)
this.drawCircle(point.x, point.y, 22)
this.endFill()
}
drawStrokes() {
this.drawBackground()
this.lineStyle(1.0, 0xFF0000, 1)
for (let stroke of this.iterStrokes()) {
this.drawStroke(stroke)
}
}
redraw() {
this.drawStrokes()
}
// Can be overwritten if different levels of strokes are necessary
*iterStrokes() {
for (let stroke of this.strokes) {
yield stroke
}
yield this.stroke
}
changed() {
// Can be overwritten
}
clearAll() {
let cmd = new ClearCommand()
cmd.do(this)
}
normalizeInfo(info) {
let { x, y, pressure, tiltX, tiltY, color } = info
x /= this.wantedWidth
y /= this.wantedHeight
return { x, y, pressure, tiltX, tiltY, color }
}
denormalizeInfo(info) {
let { x, y, pressure, tiltX, tiltY, color } = info
x = x * this.wantedWidth
y = y * this.wantedHeight
return { x, y, pressure, tiltX, tiltY, color }
}
// Convert strokes into an object that can be stored in an Indexed DB.
// Returns normalized strokes
toObject() {
let result = []
for (let stroke of this.strokes) {
let normalized = []
for (let info of stroke) {
normalized.push(this.normalizeInfo(info))
}
result.push(normalized)
}
return result
}
// Read normalized strokes from an object from an Indexed DB.
fromObject(normalizedStrokes) {
this.strokes = []
for (let stroke of normalizedStrokes) {
let denormalized = []
for (let info of stroke) {
denormalized.push(this.denormalizeInfo(info))
}
this.strokes.push(denormalized)
}
}
// Convert strokes into a JSON object that can be stored in an Indexed DB
toJSON() {
return JSON.stringify(this.toObject())
}
// Convert strokes from a JSON
fromJSON(json) {
this.fromObject(JSON.parse(json))
}
// Returns a set of used colors
usedColors() {
let used = new Set()
for (let info of this.stroke) {
used.add(info.color)
}
for (let stroke of this.strokes) {
for (let info of stroke) {
used.add(info.color)
}
}
return used.values()
}
}