drawing ink
This commit is contained in:
parent
9420e9e7bd
commit
487090cf58
|
|
@ -13,15 +13,14 @@
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
|
|
||||||
spatial-ink {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0 0 0 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<nav>
|
||||||
|
<button on:click="DRAW">Draw</button>
|
||||||
|
</nav>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
|
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
|
||||||
import { SpatialInk } from '../src/canvas/spatial-ink.ts';
|
import { SpatialInk } from '../src/canvas/spatial-ink.ts';
|
||||||
|
|
@ -29,15 +28,50 @@
|
||||||
SpatialGeometry.register();
|
SpatialGeometry.register();
|
||||||
SpatialInk.register();
|
SpatialInk.register();
|
||||||
|
|
||||||
function trace() {
|
const drawButton = document.querySelector('button');
|
||||||
|
|
||||||
|
async function draw(e) {
|
||||||
|
if (e.target === drawButton) return;
|
||||||
|
|
||||||
const ink = document.createElement('spatial-ink');
|
const ink = document.createElement('spatial-ink');
|
||||||
document.body.append(ink);
|
ink.style.cursor = 'var(--tracing-cursor, crosshair)';
|
||||||
ink.trace();
|
ink.style.position = 'fixed';
|
||||||
|
ink.style.inset = '0 0 0 0';
|
||||||
|
ink.style.zIndex = 'calc(infinity)';
|
||||||
|
|
||||||
|
document.body.appendChild(ink);
|
||||||
|
|
||||||
|
const rect = await ink.draw(e);
|
||||||
|
const geometry = document.createElement('spatial-geometry');
|
||||||
|
geometry.x = rect.x;
|
||||||
|
geometry.y = rect.y;
|
||||||
|
geometry.height = rect.height;
|
||||||
|
geometry.width = rect.width;
|
||||||
|
|
||||||
|
ink.points = ink.points.map(([x, y, p]) => [x - rect.x, y - rect.y, p]);
|
||||||
|
ink.style.cursor = '';
|
||||||
|
ink.style.position = '';
|
||||||
|
ink.style.inset = '';
|
||||||
|
ink.style.zIndex = '';
|
||||||
|
geometry.appendChild(ink);
|
||||||
|
|
||||||
|
document.body.appendChild(geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('pointerdown', trace);
|
let isDrawing = false;
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (e.target !== drawButton) return;
|
||||||
|
|
||||||
trace();
|
isDrawing = !isDrawing;
|
||||||
|
|
||||||
|
if (isDrawing) {
|
||||||
|
document.addEventListener('pointerdown', draw);
|
||||||
|
drawButton.textContent = 'Drawing';
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('pointerdown', draw);
|
||||||
|
drawButton.textContent = 'Draw';
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -29,32 +29,18 @@ styles.replaceSync(`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 20px 10px 10px;
|
|
||||||
cursor: var(--fc-move, move);
|
cursor: var(--fc-move, move);
|
||||||
content-visibility: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(*) {
|
::slotted(*) {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host > div {
|
:host(:focus-within) {
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host > div > div {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(:focus-within) > div {
|
|
||||||
outline: solid 1px hsl(214, 84%, 56%);
|
outline: solid 1px hsl(214, 84%, 56%);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(:hover) > div {
|
:host(:hover) {
|
||||||
outline: solid 2px hsl(214, 84%, 56%);
|
outline: solid 2px hsl(214, 84%, 56%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,17 +141,15 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
|
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
|
||||||
// I can see it becoming important at scale
|
// I can see it becoming important at scale
|
||||||
shadowRoot.innerHTML = `
|
shadowRoot.innerHTML = `
|
||||||
<div>
|
|
||||||
<button part="rotate"></button>
|
<button part="rotate"></button>
|
||||||
<button part="resize-nw"></button>
|
<button part="resize-nw"></button>
|
||||||
<button part="resize-ne"></button>
|
<button part="resize-ne"></button>
|
||||||
<button part="resize-se"></button>
|
<button part="resize-se"></button>
|
||||||
<button part="resize-sw"></button>
|
<button part="resize-sw"></button>
|
||||||
<div><slot></slot></div>
|
<slot></slot>`;
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#type: Shape = 'rectangle';
|
#type = (this.getAttribute('type') || 'rectangle') as Shape;
|
||||||
get type(): Shape {
|
get type(): Shape {
|
||||||
return this.#type;
|
return this.#type;
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +159,7 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#previousX = 0;
|
#previousX = 0;
|
||||||
#x = 0;
|
#x = Number(this.getAttribute('x')) || 0;
|
||||||
get x(): number {
|
get x(): number {
|
||||||
return this.#x;
|
return this.#x;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +170,7 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#previousY = 0;
|
#previousY = 0;
|
||||||
#y = 0;
|
#y = Number(this.getAttribute('y')) || 0;
|
||||||
get y(): number {
|
get y(): number {
|
||||||
return this.#y;
|
return this.#y;
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +181,7 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#previousWidth = 0;
|
#previousWidth = 0;
|
||||||
#width = 1;
|
#width = Number(this.getAttribute('width')) || 1;
|
||||||
get width(): number {
|
get width(): number {
|
||||||
return this.#width;
|
return this.#width;
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +192,7 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#previousHeight = 0;
|
#previousHeight = 0;
|
||||||
#height = 1;
|
#height = Number(this.getAttribute('height')) || 1;
|
||||||
get height(): number {
|
get height(): number {
|
||||||
return this.#height;
|
return this.#height;
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +203,7 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#previousRotate = 0;
|
#previousRotate = 0;
|
||||||
#rotate = 0;
|
#rotate = Number(this.getAttribute('rotate')) || 0;
|
||||||
get rotate(): number {
|
get rotate(): number {
|
||||||
return this.#rotate;
|
return this.#rotate;
|
||||||
}
|
}
|
||||||
|
|
@ -229,15 +213,6 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
this.#requestUpdate('rotate');
|
this.#requestUpdate('rotate');
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
this.type = (this.getAttribute('type') || 'rectangle') as Shape;
|
|
||||||
this.x = Number(this.getAttribute('x')) || 0;
|
|
||||||
this.y = Number(this.getAttribute('y')) || 0;
|
|
||||||
this.height = Number(this.getAttribute('height')) || 0;
|
|
||||||
this.width = Number(this.getAttribute('width')) || 0;
|
|
||||||
this.rotate = Number(this.getAttribute('rotate')) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
cancelAnimationFrame(this.#rAFId);
|
cancelAnimationFrame(this.#rAFId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,12 @@ export type Stroke = number[][];
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-pointer
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-pointer
|
||||||
const styles = new CSSStyleSheet();
|
const styles = new CSSStyleSheet();
|
||||||
styles.replaceSync(`
|
styles.replaceSync(`
|
||||||
svg {
|
:host, svg {
|
||||||
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(:state(tracing)) {
|
|
||||||
cursor: var(--tracing-cursor, crosshair);
|
|
||||||
position: fixed;
|
|
||||||
inset: 0 0 0 0;
|
|
||||||
z-index: calc(infinity);
|
|
||||||
}
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export class SpatialInk extends HTMLElement {
|
export class SpatialInk extends HTMLElement {
|
||||||
|
|
@ -117,12 +111,19 @@ export class SpatialInk extends HTMLElement {
|
||||||
this.#update();
|
this.#update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tracingPromise: PromiseWithResolvers<DOMRectReadOnly | undefined> | null = null;
|
||||||
|
|
||||||
// TODO: cancel trace?
|
// TODO: cancel trace?
|
||||||
trace() {
|
async draw(event: PointerEvent): Promise<DOMRectReadOnly | undefined> {
|
||||||
|
if (event.button !== 0 || event.ctrlKey) return;
|
||||||
|
|
||||||
this.points = [];
|
this.points = [];
|
||||||
this.#internals.states.add('tracing');
|
this.addPoint([event.pageX, event.pageY, event.pressure]);
|
||||||
this.addEventListener('pointerdown', this);
|
|
||||||
this.addEventListener('lostpointercapture', this);
|
this.addEventListener('lostpointercapture', this);
|
||||||
|
this.addEventListener('pointermove', this);
|
||||||
|
this.setPointerCapture(event.pointerId);
|
||||||
|
this.#tracingPromise = Promise.withResolvers<DOMRectReadOnly | undefined>();
|
||||||
|
return this.#tracingPromise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPoint(point: Point) {
|
addPoint(point: Point) {
|
||||||
|
|
@ -132,23 +133,16 @@ export class SpatialInk extends HTMLElement {
|
||||||
|
|
||||||
handleEvent(event: PointerEvent) {
|
handleEvent(event: PointerEvent) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'pointerdown': {
|
|
||||||
if (event.button !== 0 || event.ctrlKey) return;
|
|
||||||
|
|
||||||
this.addEventListener('pointermove', this);
|
|
||||||
this.setPointerCapture(event.pointerId);
|
|
||||||
this.addPoint([event.pageX, event.pageY, event.pressure]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'pointermove': {
|
case 'pointermove': {
|
||||||
this.addPoint([event.pageX, event.pageY, event.pressure]);
|
this.addPoint([event.pageX, event.pageY, event.pressure]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'lostpointercapture': {
|
case 'lostpointercapture': {
|
||||||
this.#internals.states.delete('tracing');
|
|
||||||
this.removeEventListener('pointermove', this);
|
this.removeEventListener('pointermove', this);
|
||||||
this.removeEventListener('pointerdown', this);
|
this.removeEventListener('pointerdown', this);
|
||||||
this.removeEventListener('lostpointercapture', this);
|
this.removeEventListener('lostpointercapture', this);
|
||||||
|
this.#tracingPromise?.resolve(this.#path.getBoundingClientRect());
|
||||||
|
this.#tracingPromise = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue