ink
This commit is contained in:
parent
9bfb80bd6b
commit
6781eb19ba
|
|
@ -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';
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue