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