Uups, forgot to remove files.
This commit is contained in:
parent
af932a9911
commit
b6fc04a411
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
https://mattdesl.svbtle.com/drawing-lines-is-hard
|
|
||||||
http://perfectionkills.com/exploring-canvas-drawing-techniques/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
https://github.com/mattdesl/polyline-normals
|
|
||||||
|
|
||||||
var path = [ [0, 122], [0, 190], [90, 190] ]
|
|
||||||
|
|
||||||
//get the normals as a closed loop
|
|
||||||
var normals = getNormals(path, true)
|
|
@ -1,73 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
|
|
||||||
<!-- disable zooming -->
|
|
||||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
|
|
||||||
<title>Stylus Functional Tests</title>
|
|
||||||
<style media="screen">
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
|
|
||||||
height:100%;
|
|
||||||
width: 100%;
|
|
||||||
/* See http://stackoverflow.com/questions/9280258/prevent-body-scrolling-but-allow-overlay-scrolling */
|
|
||||||
position:fixed;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 18px;
|
|
||||||
background:gray;
|
|
||||||
font-family:arial,sans-serif;
|
|
||||||
color:#FFF;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
-webkit-overflow-scrolling: auto;
|
|
||||||
user-select: none;
|
|
||||||
touch-action: manipulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
|
|
||||||
.interactive {
|
|
||||||
-ms-content-zooming: none;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
main {
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
position:relative
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
position:absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script src=".././3rdparty/all.js"></script>
|
|
||||||
<script src="../../lib/bootstrap.js"></script>
|
|
||||||
</head>
|
|
||||||
<body class="site" oncontextmenu="return false" >
|
|
||||||
<main id="main" class="container">
|
|
||||||
<canvas id="canvas" width="100%" height="100%" style="position: absolute; z-index:10000; user-select: none;">
|
|
||||||
Get a better browser, bro.
|
|
||||||
</canvas>
|
|
||||||
</main>
|
|
||||||
<script>
|
|
||||||
Bootstrap.import('./main.js')
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,90 +0,0 @@
|
|||||||
import PIXIApp from '../../lib/pixi/app.js'
|
|
||||||
import Button from '../../lib/pixi/button.js'
|
|
||||||
import ButtonGroup from '../../lib/pixi/buttongroup.js'
|
|
||||||
import Stylus from './stylus.js'
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 })
|
|
||||||
window.app = app
|
|
||||||
app.setup()
|
|
||||||
app.run()
|
|
||||||
|
|
@ -1,398 +0,0 @@
|
|||||||
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.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 = []
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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.moveStroke(this.toStroke(e))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.on('pointerup', (e) => {
|
|
||||||
if (this.debug) console.log("pointerup", e)
|
|
||||||
if (this.eventInside(e))
|
|
||||||
this.endStroke(this.toStroke(e))
|
|
||||||
})
|
|
||||||
this.on('pointerleave', (e) => {
|
|
||||||
this.endStroke(this.toStroke(e))
|
|
||||||
})
|
|
||||||
this.on('pointercancel', (e) => {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
endStroke(info) {
|
|
||||||
if (this.stroke.length > 1) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
Loading…
Reference in New Issue
Block a user