propagator tool thing

This commit is contained in:
Orion Reed 2024-12-15 06:55:35 -05:00
parent abf1d73aa6
commit 753e9f490c
4 changed files with 133 additions and 15 deletions

View File

@ -32,23 +32,27 @@
<body>
<folk-toolset>
<folk-distance-field>
<folk-shape x="100" y="100" width="50" height="50"></folk-shape>
<folk-shape x="100" y="200" width="50" height="50"></folk-shape>
<folk-shape x="100" y="300" width="50" height="50"></folk-shape>
<folk-shape x="300" y="150" width="80" height="40"></folk-shape>
<folk-shape x="400" y="250" width="60" height="90"></folk-shape>
<folk-shape x="200" y="400" width="100" height="100"></folk-shape>
<folk-shape x="500" y="100" width="30" height="70"></folk-shape>
<folk-shape x="500" y="200">
<folk-shape id="s1" x="100" y="100" width="50" height="50"></folk-shape>
<folk-shape id="s2" x="100" y="200" width="50" height="50"></folk-shape>
<folk-shape id="s3" x="100" y="300" width="50" height="50"></folk-shape>
<folk-shape id="s4" x="300" y="150" width="80" height="40"></folk-shape>
<folk-shape id="s5" x="400" y="250" width="60" height="90"></folk-shape>
<folk-shape id="s6" x="200" y="400" width="100" height="100"></folk-shape>
<folk-shape id="s7" x="500" y="100" width="30" height="70"></folk-shape>
<folk-shape id="s8" x="500" y="200">
<folk-shape-tool></folk-shape-tool>
</folk-shape>
<folk-shape x="700" y="200">
<folk-delete-tool></folk-delete-tool>
</folk-shape>
<folk-shape x="500" y="300">
<folk-propagator-tool></folk-propagator-tool>
</folk-shape>
</folk-distance-field>
</folk-toolset>
<script type="module">
import '../src/standalone/folk-event-propagator.ts';
import '../src/standalone/folk-shape.ts';
import '../src/standalone/folk-distance-field.ts';
import '../src/standalone/folk-toolset.ts';

View File

@ -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() sourceRect: DOMRectReadOnly | null = null;
@property({ type: String, reflect: true }) target = '';
@property({ type: String, reflect: true }) target?: string;
@state() targetRect: DOMRectReadOnly | null = null;
@ -41,6 +41,8 @@ export class FolkBaseConnection extends FolkElement {
if (changedProperties.has('source')) {
this.#unobserveSource();
if (!this.source) return;
const vertex = parseVertex(this.source);
if (vertex) {
@ -59,6 +61,8 @@ export class FolkBaseConnection extends FolkElement {
if (changedProperties.has('target')) {
this.#unobserveTarget();
if (!this.target) return;
const vertex = parseVertex(this.target);
if (vertex) {

View File

@ -30,6 +30,8 @@ declare global {
export class FolkRope extends FolkBaseConnection implements AnimationFrameControllerHost {
static override tagName = 'folk-rope';
static #resolution = 5;
static styles = [
FolkBaseConnection.styles,
css`
@ -41,7 +43,7 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
path {
fill: none;
pointer-events: auto;
pointer-events: none;
stroke: var(--folk-rope-color, black);
stroke-width: var(--folk-rope-width, 3);
stroke-linecap: var(--folk-rope-linecap, round);
@ -134,6 +136,38 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
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() {
if (this.#points.length < 2) return;
@ -167,9 +201,8 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
#generatePoints(start: Point, end: Point) {
const delta = Vector.sub(end, start);
const len = Vector.mag(delta);
const resolution = 5;
const points: RopePoint[] = [];
const pointsLen = Math.floor(len / resolution);
const pointsLen = Math.floor(len / FolkRope.#resolution);
for (let i = 0; i < pointsLen; i++) {
const percentage = i / (pointsLen - 1);
@ -178,7 +211,7 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
points.push({
pos,
oldPos: { ...pos },
distanceToNextPoint: resolution,
distanceToNextPoint: FolkRope.#resolution,
mass: 1,
damping: 0.99,
velocity: Vector.zero(),

View File

@ -1,3 +1,4 @@
import { FolkEventPropagator } from './folk-event-propagator';
import { FolkShape } from './folk-shape';
export abstract class FolkInteractionHandler extends HTMLElement {
@ -32,11 +33,86 @@ export abstract class FolkInteractionHandler extends HTMLElement {
}
activate() {
console.log('activate', 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 {
static tagName = 'folk-shape-tool';
readonly events = ['pointerdown', 'pointermove', 'pointerup'];
@ -213,4 +289,5 @@ export class FolkToolset extends HTMLElement {
FolkShapeTool.define();
FolkDeleteTool.define();
FolkPropagatorTool.define();
FolkToolset.define();