From 3bcd80b32a3f031db9bc0a8ded5e4b066d3ca596 Mon Sep 17 00:00:00 2001 From: Orion Reed Date: Sat, 14 Dec 2024 02:27:42 -0500 Subject: [PATCH] cut only on defocus --- src/folk-event-propagator.ts | 59 ++++++++++++++++++++++++++---------- src/folk-rope.ts | 32 ++++++++++--------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/folk-event-propagator.ts b/src/folk-event-propagator.ts index 51da853..dddcd48 100644 --- a/src/folk-event-propagator.ts +++ b/src/folk-event-propagator.ts @@ -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): 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`; } } } diff --git a/src/folk-rope.ts b/src/folk-rope.ts index 9ba9930..971572b 100644 --- a/src/folk-rope.ts +++ b/src/folk-rope.ts @@ -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örmer–Verlet 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) {