This commit is contained in:
“chrisshank” 2024-10-07 21:21:24 -04:00
parent 9bfb80bd6b
commit 6781eb19ba
4 changed files with 70 additions and 57 deletions

View File

@ -13,7 +13,6 @@
min-height: 100%;
position: relative;
margin: 0;
padding: 0;
}
</style>
</head>
@ -33,26 +32,24 @@
async function draw(e) {
if (e.target === drawButton) return;
// Stop the default focus and pointer capture of geometry elements
e.preventDefault();
e.stopPropagation();
const ink = document.createElement('spatial-ink');
ink.style.cursor = 'var(--tracing-cursor, crosshair)';
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);
await ink.draw(e);
const rect = ink.getPathBox();
ink.points = ink.points.map(([x, y, p]) => [x - rect.x, y - rect.y, p]);
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);
@ -65,10 +62,10 @@
isDrawing = !isDrawing;
if (isDrawing) {
document.addEventListener('pointerdown', draw);
document.addEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Drawing';
} else {
document.removeEventListener('pointerdown', draw);
document.removeEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Draw';
}
});

View File

@ -17,10 +17,11 @@
spatial-geometry {
background: rgb(187, 178, 178);
box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;
box-shadow: rgba(0, 0, 0, 0.2) 1.95px 1.95px 2.6px;
transition: scale 100ms ease-out, box-shadow 100ms ease-out;
}
spatial-geometry:state(moving) {
spatial-geometry:state(move) {
scale: 1.05;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}

View File

@ -51,7 +51,12 @@ styles.replaceSync(`
outline: solid 2px hsl(214, 84%, 56%);
}
:host(:state(moving)) {
:host(:state(move)),
:host(:state(rotate)),
:host(:state(resize-nw)),
:host(:state(resize-ne)),
:host(:state(resize-se)),
:host(:state(resize-sw)), {
user-select: none;
}
@ -139,9 +144,6 @@ export class SpatialGeometry extends HTMLElement {
super();
this.addEventListener('pointerdown', this);
this.addEventListener('lostpointercapture', this);
// this.addEventListener('touchstart', this);
// this.addEventListener('dragstart', this);
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
shadowRoot.adoptedStyleSheets.push(styles);
@ -255,8 +257,12 @@ export class SpatialGeometry extends HTMLElement {
if (target !== this && !target.hasAttribute('part')) return;
target.addEventListener('pointermove', this);
this.addEventListener('lostpointercapture', this);
target.setPointerCapture(event.pointerId);
this.#internals.states.add('moving');
const interaction = target.getAttribute('part') || 'move';
this.#internals.states.add(interaction);
this.focus();
return;
}
@ -308,14 +314,11 @@ export class SpatialGeometry extends HTMLElement {
return;
}
case 'lostpointercapture': {
this.#internals.states.delete('moving');
const target = event.composedPath()[0] as HTMLElement;
const interaction = target.getAttribute('part') || 'move';
this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this);
return;
}
case 'touchstart':
case 'dragstart': {
event.preventDefault();
this.removeEventListener('lostpointercapture', this);
return;
}
}

View File

@ -13,6 +13,13 @@ styles.replaceSync(`
height: 100%;
width: 100%;
touch-action: none;
pointer-events: none;
}
:host(:state(drawing)) {
position: fixed;
inset: 0 0 0 0;
cursor: var(--tracing-cursor, crosshair);
}
`);
@ -25,18 +32,14 @@ export class SpatialInk extends HTMLElement {
#internals = this.attachInternals();
#svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
#path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
#stroke: Stroke = [];
#d = '';
#size = Number(this.getAttribute('size') || 16);
get size() {
return this.#size;
}
set size(size) {
this.#size = size;
this.#update();
@ -47,7 +50,6 @@ export class SpatialInk extends HTMLElement {
get thinning() {
return this.#thinning;
}
set thinning(thinning) {
this.#thinning = thinning;
this.#update();
@ -58,7 +60,6 @@ export class SpatialInk extends HTMLElement {
get smoothing() {
return this.#smoothing;
}
set smoothing(smoothing) {
this.#smoothing = smoothing;
this.#update();
@ -69,7 +70,6 @@ export class SpatialInk extends HTMLElement {
get streamline() {
return this.#streamline;
}
set streamline(streamline) {
this.#streamline = streamline;
this.#update();
@ -80,7 +80,6 @@ export class SpatialInk extends HTMLElement {
get simulatePressure() {
return this.#simulatePressure;
}
set simulatePressure(simulatePressure) {
this.#simulatePressure = simulatePressure;
this.#update();
@ -91,7 +90,6 @@ export class SpatialInk extends HTMLElement {
get points() {
return this.#points;
}
set points(points) {
this.#points = points;
this.#update();
@ -102,27 +100,32 @@ export class SpatialInk extends HTMLElement {
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
shadowRoot.adoptedStyleSheets.push(styles);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.appendChild(this.#path);
shadowRoot.appendChild(svg);
this.#svg.appendChild(this.#path);
shadowRoot.appendChild(this.#svg);
}
connectedCallback() {
this.#update();
}
#tracingPromise: PromiseWithResolvers<DOMRectReadOnly | undefined> | null = null;
getPathBox() {
return this.#path.getBBox();
}
setViewBox() {
this.#svg.viewBox;
}
#tracingPromise: PromiseWithResolvers<void> | null = null;
// TODO: cancel trace?
async draw(event: PointerEvent): Promise<DOMRectReadOnly | undefined> {
if (event.button !== 0 || event.ctrlKey) return;
this.points = [];
this.addPoint([event.pageX, event.pageY, event.pressure]);
this.addEventListener('lostpointercapture', this);
this.addEventListener('pointermove', this);
this.setPointerCapture(event.pointerId);
this.#tracingPromise = Promise.withResolvers<DOMRectReadOnly | undefined>();
draw(event?: PointerEvent) {
if (event?.type === 'pointerdown') {
this.handleEvent(event);
} else {
this.addEventListener('pointerdown', this);
}
this.#tracingPromise = Promise.withResolvers();
return this.#tracingPromise.promise;
}
@ -133,15 +136,27 @@ export class SpatialInk extends HTMLElement {
handleEvent(event: PointerEvent) {
switch (event.type) {
case 'pointerdown': {
if (event.button !== 0 || event.ctrlKey) return;
this.points = [];
this.addPoint([event.offsetX, event.offsetY, event.pressure]);
this.addEventListener('lostpointercapture', this);
this.addEventListener('pointermove', this);
this.setPointerCapture(event.pointerId);
this.#internals.states.add('drawing');
return;
}
case 'pointermove': {
this.addPoint([event.pageX, event.pageY, event.pressure]);
this.addPoint([event.offsetX, event.offsetY, event.pressure]);
return;
}
case 'lostpointercapture': {
this.removeEventListener('pointermove', this);
this.removeEventListener('pointerdown', this);
this.removeEventListener('pointermove', this);
this.removeEventListener('lostpointercapture', this);
this.#tracingPromise?.resolve(this.#path.getBoundingClientRect());
this.#internals.states.delete('drawing');
this.#tracingPromise?.resolve();
this.#tracingPromise = null;
return;
}
@ -168,10 +183,7 @@ export class SpatialInk extends HTMLElement {
cap: true,
},
};
this.#stroke = getStroke(this.#points, options);
this.#d = this.#getSvgPathFromStroke(this.#stroke);
this.#path.setAttribute('d', this.#d);
this.#path.setAttribute('d', this.#getSvgPathFromStroke(getStroke(this.#points, options)));
}
#getSvgPathFromStroke(stroke: Stroke): string {