Removed the stylus folder and added stylus.js with doctest to lib/pixi
This commit is contained in:
parent
da212ac188
commit
af932a9911
424
dist/iwmlib.pixi.js
vendored
424
dist/iwmlib.pixi.js
vendored
@ -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;
|
||||||
|
@ -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
149
lib/pixi/stylus.html
Normal 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
422
lib/pixi/stylus.js
Executable 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user