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%; min-height: 100%;
position: relative; position: relative;
margin: 0; margin: 0;
padding: 0;
} }
</style> </style>
</head> </head>
@ -33,26 +32,24 @@
async function draw(e) { async function draw(e) {
if (e.target === drawButton) return; 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'); 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); 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'); const geometry = document.createElement('spatial-geometry');
geometry.x = rect.x; geometry.x = rect.x;
geometry.y = rect.y; geometry.y = rect.y;
geometry.height = rect.height; geometry.height = rect.height;
geometry.width = rect.width; 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); geometry.appendChild(ink);
document.body.appendChild(geometry); document.body.appendChild(geometry);
@ -65,10 +62,10 @@
isDrawing = !isDrawing; isDrawing = !isDrawing;
if (isDrawing) { if (isDrawing) {
document.addEventListener('pointerdown', draw); document.addEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Drawing'; drawButton.textContent = 'Drawing';
} else { } else {
document.removeEventListener('pointerdown', draw); document.removeEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Draw'; drawButton.textContent = 'Draw';
} }
}); });

View File

@ -17,10 +17,11 @@
spatial-geometry { spatial-geometry {
background: rgb(187, 178, 178); 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; scale: 1.05;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 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%); 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; user-select: none;
} }
@ -139,9 +144,6 @@ export class SpatialGeometry extends HTMLElement {
super(); super();
this.addEventListener('pointerdown', this); this.addEventListener('pointerdown', this);
this.addEventListener('lostpointercapture', this);
// this.addEventListener('touchstart', this);
// this.addEventListener('dragstart', this);
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
shadowRoot.adoptedStyleSheets.push(styles); shadowRoot.adoptedStyleSheets.push(styles);
@ -255,8 +257,12 @@ export class SpatialGeometry extends HTMLElement {
if (target !== this && !target.hasAttribute('part')) return; if (target !== this && !target.hasAttribute('part')) return;
target.addEventListener('pointermove', this); target.addEventListener('pointermove', this);
this.addEventListener('lostpointercapture', this);
target.setPointerCapture(event.pointerId); target.setPointerCapture(event.pointerId);
this.#internals.states.add('moving');
const interaction = target.getAttribute('part') || 'move';
this.#internals.states.add(interaction);
this.focus(); this.focus();
return; return;
} }
@ -308,14 +314,11 @@ export class SpatialGeometry extends HTMLElement {
return; return;
} }
case 'lostpointercapture': { case 'lostpointercapture': {
this.#internals.states.delete('moving');
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
const interaction = target.getAttribute('part') || 'move';
this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this); target.removeEventListener('pointermove', this);
return; this.removeEventListener('lostpointercapture', this);
}
case 'touchstart':
case 'dragstart': {
event.preventDefault();
return; return;
} }
} }

View File

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