cut only on defocus
This commit is contained in:
parent
0856204579
commit
3bcd80b32a
|
|
@ -9,8 +9,14 @@ export class FolkEventPropagator extends FolkRope {
|
||||||
static styles = [
|
static styles = [
|
||||||
...FolkRope.styles,
|
...FolkRope.styles,
|
||||||
css`
|
css`
|
||||||
textarea {
|
.input-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
translate: -50% -50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 3ch;
|
min-width: 3ch;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -21,8 +27,17 @@ export class FolkEventPropagator extends FolkRope {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
field-sizing: content;
|
field-sizing: content;
|
||||||
translate: -50% -50%;
|
box-sizing: content-box;
|
||||||
border-radius: 5px;
|
}
|
||||||
|
|
||||||
|
.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');
|
#triggerTextarea = document.createElement('textarea');
|
||||||
#expressionTextarea = document.createElement('textarea');
|
#expressionTextarea = document.createElement('textarea');
|
||||||
#propagator: Propagator | null = null;
|
#propagator: Propagator | null = null;
|
||||||
|
#container = document.createElement('div');
|
||||||
|
#hasError = false;
|
||||||
|
|
||||||
override firstUpdated(changedProperties: PropertyValues<this>): void {
|
override firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
|
|
||||||
|
this.#container.className = 'input-container';
|
||||||
|
this.#triggerTextarea.className = 'trigger';
|
||||||
|
this.#expressionTextarea.className = 'expression';
|
||||||
|
|
||||||
this.#triggerTextarea.addEventListener('change', () => {
|
this.#triggerTextarea.addEventListener('change', () => {
|
||||||
this.trigger = this.#triggerTextarea.value;
|
this.trigger = this.#triggerTextarea.value;
|
||||||
});
|
});
|
||||||
|
|
@ -45,10 +66,17 @@ export class FolkEventPropagator extends FolkRope {
|
||||||
this.expression = this.#expressionTextarea.value;
|
this.expression = this.#expressionTextarea.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.#expressionTextarea.addEventListener('focusout', () => {
|
||||||
|
if (this.#hasError) {
|
||||||
|
this.cut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.#triggerTextarea.value = this.trigger;
|
this.#triggerTextarea.value = this.trigger;
|
||||||
this.#expressionTextarea.value = this.expression;
|
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();
|
this.#initializePropagator();
|
||||||
}
|
}
|
||||||
|
|
@ -73,24 +101,23 @@ export class FolkEventPropagator extends FolkRope {
|
||||||
target: this.targetElement,
|
target: this.targetElement,
|
||||||
event: this.trigger,
|
event: this.trigger,
|
||||||
handler: this.expression,
|
handler: this.expression,
|
||||||
onParseError: () => this.cut(),
|
onParseError: () => {
|
||||||
onParseSuccess: () => this.mend(),
|
this.#hasError = true;
|
||||||
|
},
|
||||||
|
onParseSuccess: () => {
|
||||||
|
this.#hasError = false;
|
||||||
|
this.mend();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
super.render();
|
super.render();
|
||||||
|
|
||||||
const triggerPoint = this.points[Math.floor(this.points.length / 5)];
|
const point = this.getPointAt(0.5);
|
||||||
if (triggerPoint) {
|
if (point) {
|
||||||
this.#triggerTextarea.style.left = `${triggerPoint.pos.x}px`;
|
this.#container.style.left = `${point.pos.x}px`;
|
||||||
this.#triggerTextarea.style.top = `${triggerPoint.pos.y}px`;
|
this.#container.style.top = `${point.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`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import { css, PropertyValues } from '@lit/reactive-element';
|
||||||
import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts';
|
import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts';
|
||||||
import { property } from '@lit/reactive-element/decorators.js';
|
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örmer–Verlet integration to keep the simulation consistent otherwise it would "explode"!
|
// Each rope part is one of these uses a high precision variant of Störmer–Verlet integration to keep the simulation consistent otherwise it would "explode"!
|
||||||
interface RopePoint {
|
interface RopePoint {
|
||||||
pos: Point;
|
pos: Point;
|
||||||
|
|
@ -103,20 +101,20 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
let target: Point;
|
let target: Point;
|
||||||
|
|
||||||
if (sourceRect instanceof DOMRectTransform) {
|
if (sourceRect instanceof DOMRectTransform) {
|
||||||
source = sourceRect.toParentSpace({ x: sourceRect.width / 2, y: sourceRect.height });
|
source = sourceRect.center;
|
||||||
} else {
|
} else {
|
||||||
source = {
|
source = {
|
||||||
x: sourceRect.x + sourceRect.width / 2,
|
x: sourceRect.x + sourceRect.width / 2,
|
||||||
y: sourceRect.y + sourceRect.height,
|
y: sourceRect.y + sourceRect.height / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetRect instanceof DOMRectTransform) {
|
if (targetRect instanceof DOMRectTransform) {
|
||||||
target = targetRect.toParentSpace({ x: targetRect.width / 2, y: targetRect.height });
|
target = targetRect.center;
|
||||||
} else {
|
} else {
|
||||||
target = {
|
target = {
|
||||||
x: targetRect.x + targetRect.width / 2,
|
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++) {
|
for (let i = 0; i < pointsLen; i++) {
|
||||||
const percentage = i / (pointsLen - 1);
|
const percentage = i / (pointsLen - 1);
|
||||||
const pos = {
|
const pos = Vector.lerp(start, end, percentage);
|
||||||
x: lerp(start.x, end.x, percentage),
|
|
||||||
y: lerp(start.y, end.y, percentage),
|
|
||||||
};
|
|
||||||
|
|
||||||
points.push({
|
points.push({
|
||||||
pos,
|
pos,
|
||||||
|
|
@ -226,19 +221,28 @@ export class FolkRope extends FolkBaseConnection implements AnimationFrameContro
|
||||||
if (point.prev) applyConstraint(point, point.prev);
|
if (point.prev) applyConstraint(point, point.prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
cut(index = Math.floor(this.#points.length / 2)) {
|
cut(atPercentage = 0.5) {
|
||||||
if (index < 0 || index >= this.#points.length - 1) return;
|
const index = this.#getPointIndexAt(atPercentage);
|
||||||
|
|
||||||
this.#points[index].next = null;
|
this.#points[index].next = null;
|
||||||
this.#points[index + 1].prev = null;
|
this.#points[index + 1].prev = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
mend(index = Math.floor(this.#points.length / 2)) {
|
mend(atPercentage = 0.5) {
|
||||||
if (index < 0 || index >= this.#points.length - 1) return;
|
const index = this.#getPointIndexAt(atPercentage);
|
||||||
|
|
||||||
this.#points[index].next = this.#points[index + 1];
|
this.#points[index].next = this.#points[index + 1];
|
||||||
this.#points[index + 1].prev = this.#points[index];
|
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) {
|
function applyConstraint(p1: RopePoint, p2: RopePoint) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue