cut only on defocus

This commit is contained in:
Orion Reed 2024-12-14 02:27:42 -05:00
parent 0856204579
commit 3bcd80b32a
2 changed files with 61 additions and 30 deletions

View File

@ -9,8 +9,14 @@ export class FolkEventPropagator extends FolkRope {
static styles = [
...FolkRope.styles,
css`
textarea {
.input-container {
position: absolute;
display: flex;
flex-direction: column;
translate: -50% -50%;
}
textarea {
width: auto;
min-width: 3ch;
height: auto;
@ -21,8 +27,17 @@ export class FolkEventPropagator extends FolkRope {
pointer-events: auto;
overflow: hidden;
field-sizing: content;
translate: -50% -50%;
border-radius: 5px;
box-sizing: content-box;
}
.trigger {
border-radius: 5px 5px 0 0;
border-bottom: none;
width: fit-content;
}
.expression {
border-radius: 0 5px 5px 5px;
}
`,
];
@ -33,10 +48,16 @@ export class FolkEventPropagator extends FolkRope {
#triggerTextarea = document.createElement('textarea');
#expressionTextarea = document.createElement('textarea');
#propagator: Propagator | null = null;
#container = document.createElement('div');
#hasError = false;
override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);
this.#container.className = 'input-container';
this.#triggerTextarea.className = 'trigger';
this.#expressionTextarea.className = 'expression';
this.#triggerTextarea.addEventListener('change', () => {
this.trigger = this.#triggerTextarea.value;
});
@ -45,10 +66,17 @@ export class FolkEventPropagator extends FolkRope {
this.expression = this.#expressionTextarea.value;
});
this.#expressionTextarea.addEventListener('focusout', () => {
if (this.#hasError) {
this.cut();
}
});
this.#triggerTextarea.value = this.trigger;
this.#expressionTextarea.value = this.expression;
this.renderRoot.append(this.#triggerTextarea, this.#expressionTextarea);
this.#container.append(this.#triggerTextarea, this.#expressionTextarea);
this.renderRoot.append(this.#container);
this.#initializePropagator();
}
@ -73,24 +101,23 @@ export class FolkEventPropagator extends FolkRope {
target: this.targetElement,
event: this.trigger,
handler: this.expression,
onParseError: () => this.cut(),
onParseSuccess: () => this.mend(),
onParseError: () => {
this.#hasError = true;
},
onParseSuccess: () => {
this.#hasError = false;
this.mend();
},
});
}
override render() {
super.render();
const triggerPoint = this.points[Math.floor(this.points.length / 5)];
if (triggerPoint) {
this.#triggerTextarea.style.left = `${triggerPoint.pos.x}px`;
this.#triggerTextarea.style.top = `${triggerPoint.pos.y}px`;
}
const expressionPoint = this.points[Math.floor(this.points.length / 2)];
if (expressionPoint) {
this.#expressionTextarea.style.left = `${expressionPoint.pos.x}px`;
this.#expressionTextarea.style.top = `${expressionPoint.pos.y}px`;
const point = this.getPointAt(0.5);
if (point) {
this.#container.style.left = `${point.pos.x}px`;
this.#container.style.top = `${point.pos.y}px`;
}
}
}

View File

@ -8,8 +8,6 @@ import { css, PropertyValues } from '@lit/reactive-element';
import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts';
import { property } from '@lit/reactive-element/decorators.js';
const lerp = (first: number, second: number, percentage: number) => first + (second - first) * percentage;
// Each rope part is one of these uses a high precision variant of StörmerVerlet integration to keep the simulation consistent otherwise it would "explode"!
interface RopePoint {
pos: Point;
@ -103,20 +101,20 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
let target: Point;
if (sourceRect instanceof DOMRectTransform) {
source = sourceRect.toParentSpace({ x: sourceRect.width / 2, y: sourceRect.height });
source = sourceRect.center;
} else {
source = {
x: sourceRect.x + sourceRect.width / 2,
y: sourceRect.y + sourceRect.height,
y: sourceRect.y + sourceRect.height / 2,
};
}
if (targetRect instanceof DOMRectTransform) {
target = targetRect.toParentSpace({ x: targetRect.width / 2, y: targetRect.height });
target = targetRect.center;
} else {
target = {
x: targetRect.x + targetRect.width / 2,
y: targetRect.y + targetRect.height,
y: targetRect.y + targetRect.height / 2,
};
}
@ -173,10 +171,7 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
for (let i = 0; i < pointsLen; i++) {
const percentage = i / (pointsLen - 1);
const pos = {
x: lerp(start.x, end.x, percentage),
y: lerp(start.y, end.y, percentage),
};
const pos = Vector.lerp(start, end, percentage);
points.push({
pos,
@ -226,19 +221,28 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
if (point.prev) applyConstraint(point, point.prev);
}
cut(index = Math.floor(this.#points.length / 2)) {
if (index < 0 || index >= this.#points.length - 1) return;
cut(atPercentage = 0.5) {
const index = this.#getPointIndexAt(atPercentage);
this.#points[index].next = null;
this.#points[index + 1].prev = null;
}
mend(index = Math.floor(this.#points.length / 2)) {
if (index < 0 || index >= this.#points.length - 1) return;
mend(atPercentage = 0.5) {
const index = this.#getPointIndexAt(atPercentage);
this.#points[index].next = this.#points[index + 1];
this.#points[index + 1].prev = this.#points[index];
}
getPointAt(percentage: number) {
return this.#points[this.#getPointIndexAt(percentage)];
}
#getPointIndexAt(percentage: number) {
const clamped = Math.min(Math.max(percentage, 0), 1);
return Math.floor(this.#points.length * clamped);
}
}
function applyConstraint(p1: RopePoint, p2: RopePoint) {