propagator tool thing
This commit is contained in:
parent
abf1d73aa6
commit
753e9f490c
|
|
@ -32,23 +32,27 @@
|
||||||
<body>
|
<body>
|
||||||
<folk-toolset>
|
<folk-toolset>
|
||||||
<folk-distance-field>
|
<folk-distance-field>
|
||||||
<folk-shape x="100" y="100" width="50" height="50"></folk-shape>
|
<folk-shape id="s1" x="100" y="100" width="50" height="50"></folk-shape>
|
||||||
<folk-shape x="100" y="200" width="50" height="50"></folk-shape>
|
<folk-shape id="s2" x="100" y="200" width="50" height="50"></folk-shape>
|
||||||
<folk-shape x="100" y="300" width="50" height="50"></folk-shape>
|
<folk-shape id="s3" x="100" y="300" width="50" height="50"></folk-shape>
|
||||||
<folk-shape x="300" y="150" width="80" height="40"></folk-shape>
|
<folk-shape id="s4" x="300" y="150" width="80" height="40"></folk-shape>
|
||||||
<folk-shape x="400" y="250" width="60" height="90"></folk-shape>
|
<folk-shape id="s5" x="400" y="250" width="60" height="90"></folk-shape>
|
||||||
<folk-shape x="200" y="400" width="100" height="100"></folk-shape>
|
<folk-shape id="s6" x="200" y="400" width="100" height="100"></folk-shape>
|
||||||
<folk-shape x="500" y="100" width="30" height="70"></folk-shape>
|
<folk-shape id="s7" x="500" y="100" width="30" height="70"></folk-shape>
|
||||||
<folk-shape x="500" y="200">
|
<folk-shape id="s8" x="500" y="200">
|
||||||
<folk-shape-tool></folk-shape-tool>
|
<folk-shape-tool></folk-shape-tool>
|
||||||
</folk-shape>
|
</folk-shape>
|
||||||
<folk-shape x="700" y="200">
|
<folk-shape x="700" y="200">
|
||||||
<folk-delete-tool></folk-delete-tool>
|
<folk-delete-tool></folk-delete-tool>
|
||||||
</folk-shape>
|
</folk-shape>
|
||||||
|
<folk-shape x="500" y="300">
|
||||||
|
<folk-propagator-tool></folk-propagator-tool>
|
||||||
|
</folk-shape>
|
||||||
</folk-distance-field>
|
</folk-distance-field>
|
||||||
</folk-toolset>
|
</folk-toolset>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
import '../src/standalone/folk-event-propagator.ts';
|
||||||
import '../src/standalone/folk-shape.ts';
|
import '../src/standalone/folk-shape.ts';
|
||||||
import '../src/standalone/folk-distance-field.ts';
|
import '../src/standalone/folk-distance-field.ts';
|
||||||
import '../src/standalone/folk-toolset.ts';
|
import '../src/standalone/folk-toolset.ts';
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ export class FolkBaseConnection extends FolkElement {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: String, reflect: true }) source = '';
|
@property({ type: String, reflect: true }) source?: string;
|
||||||
|
|
||||||
@state() sourceElement: Element | null = null;
|
@state() sourceElement: Element | null = null;
|
||||||
|
|
||||||
@state() sourceRect: DOMRectReadOnly | null = null;
|
@state() sourceRect: DOMRectReadOnly | null = null;
|
||||||
|
|
||||||
@property({ type: String, reflect: true }) target = '';
|
@property({ type: String, reflect: true }) target?: string;
|
||||||
|
|
||||||
@state() targetRect: DOMRectReadOnly | null = null;
|
@state() targetRect: DOMRectReadOnly | null = null;
|
||||||
|
|
||||||
|
|
@ -41,6 +41,8 @@ export class FolkBaseConnection extends FolkElement {
|
||||||
if (changedProperties.has('source')) {
|
if (changedProperties.has('source')) {
|
||||||
this.#unobserveSource();
|
this.#unobserveSource();
|
||||||
|
|
||||||
|
if (!this.source) return;
|
||||||
|
|
||||||
const vertex = parseVertex(this.source);
|
const vertex = parseVertex(this.source);
|
||||||
|
|
||||||
if (vertex) {
|
if (vertex) {
|
||||||
|
|
@ -59,6 +61,8 @@ export class FolkBaseConnection extends FolkElement {
|
||||||
if (changedProperties.has('target')) {
|
if (changedProperties.has('target')) {
|
||||||
this.#unobserveTarget();
|
this.#unobserveTarget();
|
||||||
|
|
||||||
|
if (!this.target) return;
|
||||||
|
|
||||||
const vertex = parseVertex(this.target);
|
const vertex = parseVertex(this.target);
|
||||||
|
|
||||||
if (vertex) {
|
if (vertex) {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ declare global {
|
||||||
export class FolkRope extends FolkBaseConnection implements AnimationFrameControllerHost {
|
export class FolkRope extends FolkBaseConnection implements AnimationFrameControllerHost {
|
||||||
static override tagName = 'folk-rope';
|
static override tagName = 'folk-rope';
|
||||||
|
|
||||||
|
static #resolution = 5;
|
||||||
|
|
||||||
static styles = [
|
static styles = [
|
||||||
FolkBaseConnection.styles,
|
FolkBaseConnection.styles,
|
||||||
css`
|
css`
|
||||||
|
|
@ -41,7 +43,7 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: none;
|
fill: none;
|
||||||
pointer-events: auto;
|
pointer-events: none;
|
||||||
stroke: var(--folk-rope-color, black);
|
stroke: var(--folk-rope-color, black);
|
||||||
stroke-width: var(--folk-rope-width, 3);
|
stroke-width: var(--folk-rope-width, 3);
|
||||||
stroke-linecap: var(--folk-rope-linecap, round);
|
stroke-linecap: var(--folk-rope-linecap, round);
|
||||||
|
|
@ -134,6 +136,38 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
endingPoint.pos = target;
|
endingPoint.pos = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** add/remove points based on distance between source and target rects */
|
||||||
|
stretch() {
|
||||||
|
if (this.sourceRect === null || this.targetRect === null || this.#points.length < 2) return;
|
||||||
|
|
||||||
|
// Calculate desired length based on source and target positions
|
||||||
|
const distance = Vector.distance(this.sourceRect, this.targetRect);
|
||||||
|
const desiredPoints = Math.floor(distance / FolkRope.#resolution);
|
||||||
|
|
||||||
|
while (this.#points.length < desiredPoints) {
|
||||||
|
const lastPoint = this.#points.at(-1)!;
|
||||||
|
lastPoint.isFixed = false;
|
||||||
|
const newPoint = {
|
||||||
|
pos: { ...lastPoint.pos },
|
||||||
|
oldPos: { ...lastPoint.pos },
|
||||||
|
distanceToNextPoint: FolkRope.#resolution,
|
||||||
|
mass: 1,
|
||||||
|
damping: 0.99,
|
||||||
|
velocity: Vector.zero(),
|
||||||
|
isFixed: true,
|
||||||
|
prev: lastPoint,
|
||||||
|
next: null,
|
||||||
|
};
|
||||||
|
lastPoint.next = newPoint;
|
||||||
|
this.#points.push(newPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.#points.length > desiredPoints) {
|
||||||
|
this.#points.pop();
|
||||||
|
this.#points.at(-1)!.isFixed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.#points.length < 2) return;
|
if (this.#points.length < 2) return;
|
||||||
|
|
||||||
|
|
@ -167,9 +201,8 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
#generatePoints(start: Point, end: Point) {
|
#generatePoints(start: Point, end: Point) {
|
||||||
const delta = Vector.sub(end, start);
|
const delta = Vector.sub(end, start);
|
||||||
const len = Vector.mag(delta);
|
const len = Vector.mag(delta);
|
||||||
const resolution = 5;
|
|
||||||
const points: RopePoint[] = [];
|
const points: RopePoint[] = [];
|
||||||
const pointsLen = Math.floor(len / resolution);
|
const pointsLen = Math.floor(len / FolkRope.#resolution);
|
||||||
|
|
||||||
for (let i = 0; i < pointsLen; i++) {
|
for (let i = 0; i < pointsLen; i++) {
|
||||||
const percentage = i / (pointsLen - 1);
|
const percentage = i / (pointsLen - 1);
|
||||||
|
|
@ -178,7 +211,7 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
points.push({
|
points.push({
|
||||||
pos,
|
pos,
|
||||||
oldPos: { ...pos },
|
oldPos: { ...pos },
|
||||||
distanceToNextPoint: resolution,
|
distanceToNextPoint: FolkRope.#resolution,
|
||||||
mass: 1,
|
mass: 1,
|
||||||
damping: 0.99,
|
damping: 0.99,
|
||||||
velocity: Vector.zero(),
|
velocity: Vector.zero(),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { FolkEventPropagator } from './folk-event-propagator';
|
||||||
import { FolkShape } from './folk-shape';
|
import { FolkShape } from './folk-shape';
|
||||||
|
|
||||||
export abstract class FolkInteractionHandler extends HTMLElement {
|
export abstract class FolkInteractionHandler extends HTMLElement {
|
||||||
|
|
@ -32,11 +33,86 @@ export abstract class FolkInteractionHandler extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
console.log('activate', this);
|
|
||||||
FolkToolset.setActiveTool(this);
|
FolkToolset.setActiveTool(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FolkPropagatorTool extends FolkInteractionHandler {
|
||||||
|
static tagName = 'folk-propagator-tool';
|
||||||
|
readonly events = ['pointerdown', 'pointermove', 'pointerup'];
|
||||||
|
|
||||||
|
private currentPropagator: FolkEventPropagator | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.button.textContent = 'Create Propagator';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event: Event): void {
|
||||||
|
if (!(event instanceof PointerEvent)) return;
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case 'pointerdown':
|
||||||
|
if (!target || target instanceof FolkEventPropagator || target instanceof FolkInteractionHandler) return;
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!target.id) {
|
||||||
|
target.id = `folk-source-${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPropagator = new FolkEventPropagator();
|
||||||
|
this.currentPropagator.source = `#${target.id}`;
|
||||||
|
|
||||||
|
document.body.appendChild(this.currentPropagator);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pointermove':
|
||||||
|
if (!this.currentPropagator) return;
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
|
// Update the target position to follow the mouse
|
||||||
|
const rect = document.body.getBoundingClientRect();
|
||||||
|
const targetPoint = `${event.clientX - rect.left}, ${event.clientY - rect.top}`;
|
||||||
|
this.currentPropagator.target = targetPoint;
|
||||||
|
this.currentPropagator.stretch();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pointerup':
|
||||||
|
if (!this.currentPropagator) return;
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
|
const finalTarget = document.elementFromPoint(event.clientX, event.clientY) as HTMLElement;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!finalTarget ||
|
||||||
|
finalTarget instanceof FolkEventPropagator ||
|
||||||
|
finalTarget instanceof FolkInteractionHandler
|
||||||
|
) {
|
||||||
|
this.currentPropagator.remove();
|
||||||
|
} else {
|
||||||
|
if (!finalTarget.id) {
|
||||||
|
finalTarget.id = `folk-target-${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPropagator.target = `#${finalTarget.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPropagator = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static define() {
|
||||||
|
if (!customElements.get(this.tagName)) {
|
||||||
|
customElements.define(this.tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this line at the bottom of the file with the other define() calls
|
||||||
|
|
||||||
export class FolkShapeTool extends FolkInteractionHandler {
|
export class FolkShapeTool extends FolkInteractionHandler {
|
||||||
static tagName = 'folk-shape-tool';
|
static tagName = 'folk-shape-tool';
|
||||||
readonly events = ['pointerdown', 'pointermove', 'pointerup'];
|
readonly events = ['pointerdown', 'pointermove', 'pointerup'];
|
||||||
|
|
@ -213,4 +289,5 @@ export class FolkToolset extends HTMLElement {
|
||||||
|
|
||||||
FolkShapeTool.define();
|
FolkShapeTool.define();
|
||||||
FolkDeleteTool.define();
|
FolkDeleteTool.define();
|
||||||
|
FolkPropagatorTool.define();
|
||||||
FolkToolset.define();
|
FolkToolset.define();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue