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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Shapes</title>
|
<title>Shapes</title>
|
||||||
<style>
|
<style>
|
||||||
@import url('../src/elements/main.css');
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +16,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
spatial-geometry {
|
spatial-geometry {
|
||||||
border: 2px solid black;
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: rgb(187, 178, 178);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</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?
|
// Can we make adding new shapes extensible via a static property?
|
||||||
const shapes = new Set(['rectangle', 'circle', 'triangle']);
|
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?
|
// Should the move event bubble?
|
||||||
export class MoveEvent extends CustomEvent<Vector> {
|
export class MoveEvent extends CustomEvent<MoveEventDetail> {
|
||||||
constructor(vector: Vector) {
|
constructor(vector: MoveEventDetail) {
|
||||||
super('move', { detail: vector, cancelable: true, bubbles: true });
|
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?
|
// TODO: add z coordinate?
|
||||||
export class SpatialGeometry extends HTMLElement {
|
export class SpatialGeometry extends HTMLElement {
|
||||||
static tagName = 'spatial-geometry';
|
static tagName = 'spatial-geometry';
|
||||||
|
|
@ -29,6 +139,9 @@ export class SpatialGeometry extends HTMLElement {
|
||||||
this.addEventListener('lostpointercapture', this);
|
this.addEventListener('lostpointercapture', this);
|
||||||
this.addEventListener('touchstart', this);
|
this.addEventListener('touchstart', this);
|
||||||
this.addEventListener('dragstart', this);
|
this.addEventListener('dragstart', this);
|
||||||
|
|
||||||
|
const shadowRoot = this.attachShadow({ mode: 'open' });
|
||||||
|
shadowRoot.adoptedStyleSheets.push(styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
#type: Shape = 'rectangle';
|
#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.
|
// 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 {
|
getBoundingPath(): string {
|
||||||
return '';
|
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) {
|
handleEvent(event: PointerEvent) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'pointerdown': {
|
case 'pointerdown': {
|
||||||
if (event.button !== 0 || event.ctrlKey) return;
|
if (event.button !== 0 || event.ctrlKey) return;
|
||||||
|
|
||||||
this.addEventListener('pointermove', this);
|
const target = event.composedPath()[0] as HTMLElement;
|
||||||
this.setPointerCapture(event.pointerId);
|
|
||||||
|
target.addEventListener('pointermove', this);
|
||||||
|
target.setPointerCapture(event.pointerId);
|
||||||
|
this.setAttribute('selected', '');
|
||||||
|
this.#createResizeHandlers();
|
||||||
this.style.userSelect = 'none';
|
this.style.userSelect = 'none';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'pointermove': {
|
case 'pointermove': {
|
||||||
this.x += event.movementX;
|
if (event.target === this) {
|
||||||
this.y += event.movementY;
|
this.x += event.movementX;
|
||||||
|
this.y += event.movementY;
|
||||||
|
} else if ((event.target as HTMLElement).matches('[resize-handler]')) {
|
||||||
|
console.log('resizing');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'lostpointercapture': {
|
case 'lostpointercapture': {
|
||||||
this.style.userSelect = '';
|
this.style.userSelect = '';
|
||||||
this.removeEventListener('pointermove', this);
|
const target = event.composedPath()[0] as HTMLElement;
|
||||||
|
target.removeEventListener('pointermove', this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'touchstart':
|
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