render resize handlers
This commit is contained in:
parent
47c4381791
commit
929f3384f9
|
|
@ -5,8 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Shapes</title>
|
||||
<style>
|
||||
@import url('../src/elements/main.css');
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -18,7 +16,9 @@
|
|||
}
|
||||
|
||||
spatial-geometry {
|
||||
border: 2px solid black;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: rgb(187, 178, 178);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
spatial-canvas {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
spatial-geometry {
|
||||
display: block;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
content-visibility: auto;
|
||||
}
|
||||
|
|
@ -3,15 +3,125 @@ export type Shape = 'rectangle' | 'circle' | 'triangle';
|
|||
// Can we make adding new shapes extensible via a static property?
|
||||
const shapes = new Set(['rectangle', 'circle', 'triangle']);
|
||||
|
||||
export type Vector = { x: number; y: number; movementX: number; movementY: number };
|
||||
export type MoveEventDetail = { x: number; y: number; movementX: number; movementY: number };
|
||||
|
||||
// Should the move event bubble?
|
||||
export class MoveEvent extends CustomEvent<Vector> {
|
||||
constructor(vector: Vector) {
|
||||
export class MoveEvent extends CustomEvent<MoveEventDetail> {
|
||||
constructor(vector: MoveEventDetail) {
|
||||
super('move', { detail: vector, cancelable: true, bubbles: true });
|
||||
}
|
||||
}
|
||||
|
||||
const styles = new CSSStyleSheet();
|
||||
styles.replaceSync(`
|
||||
:host {
|
||||
display: block;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host(:not([selected])) [resize-handler] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([selected]) [resize-handler] {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
background: hsl(210, 20%, 98%);
|
||||
|
||||
&[resize-handler="top-left"],
|
||||
&[resize-handler="top-right"],
|
||||
&[resize-handler="bottom-right"],
|
||||
&[resize-handler="bottom-left"] {
|
||||
width: 13px;
|
||||
aspect-ratio: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1.5px solid hsl(214, 84%, 56%);
|
||||
border-radius: 2px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&[resize-handler="top-left"] {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&[resize-handler="top-right"] {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
&[resize-handler="bottom-right"] {
|
||||
top: 100%;
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
&[resize-handler="bottom-left"] {
|
||||
top: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&[resize-handler="top-left"], &[resize-handler="bottom-right"] {
|
||||
cursor: url("data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='-0.9999999999999999' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(90 16 16)' filter='url(%23shadow)'><path d='m19.7432 17.0869-4.072 4.068 2.829 2.828-8.473-.013-.013-8.47 2.841 2.842 4.075-4.068 1.414-1.415-2.844-2.842h8.486v8.484l-2.83-2.827z' fill='%23fff'/><path d='m18.6826 16.7334-4.427 4.424 1.828 1.828-5.056-.016-.014-5.054 1.842 1.841 4.428-4.422 2.474-2.475-1.844-1.843h5.073v5.071l-1.83-1.828z' fill='%23000'/></g></svg>") 16 16, pointer;
|
||||
}
|
||||
|
||||
&[resize-handler="top-right"], &[resize-handler="bottom-left"] {
|
||||
cursor: url("data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m19.7432 17.0869-4.072 4.068 2.829 2.828-8.473-.013-.013-8.47 2.841 2.842 4.075-4.068 1.414-1.415-2.844-2.842h8.486v8.484l-2.83-2.827z' fill='%23fff'/><path d='m18.6826 16.7334-4.427 4.424 1.828 1.828-5.056-.016-.014-5.054 1.842 1.841 4.428-4.422 2.474-2.475-1.844-1.843h5.073v5.071l-1.83-1.828z' fill='%23000'/></g></svg>") 16 16, pointer;
|
||||
}
|
||||
|
||||
&[resize-handler="top"],
|
||||
&[resize-handler="right"],
|
||||
&[resize-handler="bottom"],
|
||||
&[resize-handler="left"] {
|
||||
background-color: hsl(214, 84%, 56%);
|
||||
background-clip: content-box;
|
||||
border: unset;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&[resize-handler="top"] {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
&[resize-handler="right"] {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transform: translate(50%, 0);
|
||||
}
|
||||
|
||||
&[resize-handler="bottom"] {
|
||||
bottom:0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translate(0, 50%);
|
||||
}
|
||||
|
||||
&[resize-handler="left"] {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
&[resize-handler="top"], &[resize-handler="bottom"] {
|
||||
height: 6px;
|
||||
padding: 2px 0;
|
||||
cursor: url("data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='-0.9999999999999999' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(90 16 16)' filter='url(%23shadow)'><path d='m9 17.9907v.005l5.997 5.996.001-3.999h1.999 2.02v4l5.98-6.001-5.98-5.999.001 4.019-2.021.002h-2l.001-4.022zm1.411.003 3.587-3.588-.001 2.587h3.5 2.521v-2.585l3.565 3.586-3.564 3.585-.001-2.585h-2.521l-3.499-.001-.001 2.586z' fill='%23fff'/><path d='m17.4971 18.9932h2.521v2.586l3.565-3.586-3.565-3.585v2.605h-2.521-3.5v-2.607l-3.586 3.587 3.586 3.586v-2.587z' fill='%23000'/></g></svg>") 16 16, pointer;
|
||||
}
|
||||
|
||||
&[resize-handler="right"], &[resize-handler="left"] {
|
||||
width: 6px;
|
||||
padding: 0 2px;
|
||||
cursor: url("data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m9 17.9907v.005l5.997 5.996.001-3.999h1.999 2.02v4l5.98-6.001-5.98-5.999.001 4.019-2.021.002h-2l.001-4.022zm1.411.003 3.587-3.588-.001 2.587h3.5 2.521v-2.585l3.565 3.586-3.564 3.585-.001-2.585h-2.521l-3.499-.001-.001 2.586z' fill='%23fff'/><path d='m17.4971 18.9932h2.521v2.586l3.565-3.586-3.565-3.585v2.605h-2.521-3.5v-2.607l-3.586 3.587 3.586 3.586v-2.587z' fill='%23000'/></g></svg>") 16 16, pointer;
|
||||
}
|
||||
}`);
|
||||
|
||||
// TODO: add z coordinate?
|
||||
export class SpatialGeometry extends HTMLElement {
|
||||
static tagName = 'spatial-geometry';
|
||||
|
|
@ -29,6 +139,9 @@ export class SpatialGeometry extends HTMLElement {
|
|||
this.addEventListener('lostpointercapture', this);
|
||||
this.addEventListener('touchstart', this);
|
||||
this.addEventListener('dragstart', this);
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
shadowRoot.adoptedStyleSheets.push(styles);
|
||||
}
|
||||
|
||||
#type: Shape = 'rectangle';
|
||||
|
|
@ -82,29 +195,42 @@ export class SpatialGeometry extends HTMLElement {
|
|||
}
|
||||
|
||||
// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
|
||||
// We might also want some kind of utility function that maps a path into an approximate set of vertices.
|
||||
getBoundingPath(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We might also want some kind of utility function that maps a path into an approximate set of vertices.
|
||||
getBoundingVertices() {
|
||||
return [];
|
||||
}
|
||||
|
||||
handleEvent(event: PointerEvent) {
|
||||
switch (event.type) {
|
||||
case 'pointerdown': {
|
||||
if (event.button !== 0 || event.ctrlKey) return;
|
||||
|
||||
this.addEventListener('pointermove', this);
|
||||
this.setPointerCapture(event.pointerId);
|
||||
const target = event.composedPath()[0] as HTMLElement;
|
||||
|
||||
target.addEventListener('pointermove', this);
|
||||
target.setPointerCapture(event.pointerId);
|
||||
this.setAttribute('selected', '');
|
||||
this.#createResizeHandlers();
|
||||
this.style.userSelect = 'none';
|
||||
return;
|
||||
}
|
||||
case 'pointermove': {
|
||||
this.x += event.movementX;
|
||||
this.y += event.movementY;
|
||||
if (event.target === this) {
|
||||
this.x += event.movementX;
|
||||
this.y += event.movementY;
|
||||
} else if ((event.target as HTMLElement).matches('[resize-handler]')) {
|
||||
console.log('resizing');
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'lostpointercapture': {
|
||||
this.style.userSelect = '';
|
||||
this.removeEventListener('pointermove', this);
|
||||
const target = event.composedPath()[0] as HTMLElement;
|
||||
target.removeEventListener('pointermove', this);
|
||||
return;
|
||||
}
|
||||
case 'touchstart':
|
||||
|
|
@ -169,4 +295,21 @@ export class SpatialGeometry extends HTMLElement {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#firstSelection = true;
|
||||
#createResizeHandlers() {
|
||||
// lazily create resize handlers on first selection
|
||||
if (this.#firstSelection && this.shadowRoot !== null) {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<button resize-handler="top-left"></button>
|
||||
<button resize-handler="top"></button>
|
||||
<button resize-handler="top-right"></button>
|
||||
<button resize-handler="right"></button>
|
||||
<button resize-handler="bottom-right"></button>
|
||||
<button resize-handler="bottom"></button>
|
||||
<button resize-handler="bottom-left"></button>
|
||||
<button resize-handler="left"></button>`;
|
||||
this.#firstSelection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue