event-propagator

This commit is contained in:
“chrisshank” 2024-10-22 16:55:36 -07:00
parent 0c4f8f61d6
commit 18e6051e2b
4 changed files with 112 additions and 156 deletions

View File

@ -1,61 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Arrow</title>
<style>
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
min-height: 100%;
position: relative;
margin: 0;
}
spatial-geometry {
border: 2px solid black;
}
scoped-propagator {
display: block;
position: absolute;
inset: 0 0 0 0;
}
scoped-propagator::before {
display: block;
position: absolute;
content: '';
top: 0;
left: 0;
width: 32px;
height: 32px;
background-color: #33fbb8;
z-index: 99;
/* clip-path: path(
'M -6.913368606024928e-7 27.51256501186839 Q -6.913368606024928e-7 27.51256501186839 1.359391610290277 24.43576272048253 2.7187839119174146 21.358960429096673 5.396027436211911 17.2753695050713 8.073270960506408 13.191778581045929 10.536655087669818 9.709110210676396 13.000039214833228 6.2264418403068635 15.453768935769713 3.035480486305171 17.9074986567062 -0.15548086769652114 22.182098508796855 0.007962989877089266 26.456698360887508 0.17140684745069967 27.371911373232294 4.903006516643652 28.287124385577076 9.634606185836605 28.926106472791236 13.605596897003824 29.565088560005396 17.576587608171042 30.15753125011536 22.47061103218726 30.74997394022532 27.36463445620348 31.279230358053507 33.121986214585036 31.808486775881693 38.879337972966596 31.804101202822718 38.88173937858506 31.799715629763742 38.884140784203524 31.795330056704767 38.88654218982199 31.79094448364579 38.88894359544045 30.80252394827323 35.37867790823896 29.814103412900668 31.86841222103747 28.474780728720454 27.518762878054673 27.13545804454024 23.169113535071872 25.69771304378478 18.851799317580724 24.259968043029325 14.534485100089574 23.939341836207902 9.73061829847805 23.618715629386475 4.926751496866526 20.696699964783946 7.690250568827012 17.77468430018142 10.453749640787498 14.807561162025006 13.314144648523126 11.840438023868591 16.174539656258755 8.503978911444339 19.686222841120102 5.167519799020085 23.19790602598145 2.591755850647223 25.361240453345594 0.015991902274360602 27.524574880709736 0.00799560546875 27.518569946289062 Z'
); */
}
</style>
</head>
<body>
<spatial-geometry id="box1" x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box2" x="200" y="200" width="50" height="50"></spatial-geometry>
<scoped-propagator source="#box1" target="#box2"></scoped-propagator>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { ScopedPropagator } from '../src/arrows/scoped-propagator.ts';
SpatialGeometry.register();
ScopedPropagator.register();
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Propagator</title>
<style>
html {
height: 100%;
}
body {
min-height: 100%;
position: relative;
margin: 0;
}
spatial-geometry {
background: rgb(187, 178, 178);
}
event-propagator {
display: block;
position: absolute;
inset: 0 0 0 0;
}
</style>
</head>
<body>
<spatial-geometry id="box1" x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box2" x="200" y="300" width="50" height="50">Hello World</spatial-geometry>
<event-propagator
source="#box1"
target="#box2"
triggers="click"
expression="$target.textContent += '!'"
></event-propagator>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { EventPropagator } from '../src/arrows/event-propagator.ts';
SpatialGeometry.register();
EventPropagator.register();
</script>
</body>
</html>

View File

@ -0,0 +1,65 @@
import { SpatialConnection } from './spatial-connection';
export class EventPropagator extends SpatialConnection {
static tagName = 'event-propagator';
#triggers = (this.getAttribute('triggers') || '').split(',');
get triggers() {
return this.#triggers;
}
set triggers(triggers) {
this.#triggers = triggers;
}
#expression = '';
#function = new Function();
get expression() {
return this.#expression;
}
set expression(expression) {
this.#expression = expression;
this.#function = new Function('$source', '$target', '$event', expression);
}
constructor() {
super();
this.expression = this.getAttribute('expression') || '';
}
observeSource() {
super.observeSource();
for (const trigger of this.#triggers) {
// TODO: add special triggers for intersection, rAF, etc.
this.sourceElement?.addEventListener(trigger, this.evaluateExpression);
}
this.evaluateExpression();
}
unobserveSource() {
super.unobserveSource();
for (const trigger of this.#triggers) {
// TODO: add special triggers for intersection, rAF, etc.
this.sourceElement?.removeEventListener(trigger, this.evaluateExpression);
}
}
observeTarget() {
super.observeTarget();
this.evaluateExpression();
}
unobserveTarget() {
super.unobserveTarget();
}
// Do we need the event at all?
evaluateExpression = (event?: Event) => {
if (this.sourceElement === null || this.targetElement === null) return;
this.#function(this.sourceElement, this.targetElement, event);
};
}

View File

@ -1,95 +0,0 @@
import { getBoxToBoxArrow } from 'perfect-arrows';
import { AbstractArrow } from './abstract-arrow';
import { pointsOnBezierCurves } from './points-on-path';
import getStroke, { StrokeOptions } from 'perfect-freehand';
export type Arrow = [
/** The x position of the (padded) starting point. */
sx: number,
/** The y position of the (padded) starting point. */
sy: number,
/** The x position of the control point. */
cx: number,
/** The y position of the control point. */
cy: number,
/** The x position of the (padded) ending point. */
ex: number,
/** The y position of the (padded) ending point. */
ey: number,
/** The angle (in radians) for an ending arrowhead. */
ae: number,
/** The angle (in radians) for a starting arrowhead. */
as: number,
/** The angle (in radians) for a center arrowhead. */
ac: number
];
export class ScopedPropagator extends AbstractArrow {
static tagName = 'scoped-propagator';
#options: StrokeOptions = {
size: 10,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: true,
// TODO: figure out how to expose these as attributes
easing: (t) => t,
start: {
taper: 50,
easing: (t) => t,
cap: true,
},
end: {
taper: 0,
easing: (t) => t,
cap: true,
},
};
render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) {
const [sx, sy, cx, cy, ex, ey, ae] = getBoxToBoxArrow(
sourceRect.x,
sourceRect.y,
sourceRect.width,
sourceRect.height,
targetRect.x,
targetRect.y,
targetRect.width,
targetRect.height
) as Arrow;
const points = pointsOnBezierCurves([
[sx, sy],
[cx, cy],
[ex, ey],
[ex, ey],
]);
const stroke = getStroke(points, this.#options);
const path = getSvgPathFromStroke(stroke);
this.style.clipPath = `path('${path}')`;
this.style.backgroundColor = 'black';
}
}
function getSvgPathFromStroke(stroke: number[][]): string {
if (stroke.length === 0) return '';
for (const point of stroke) {
point[0] = Math.round(point[0] * 100) / 100;
point[1] = Math.round(point[1] * 100) / 100;
}
const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
const [x1, y1] = arr[(i + 1) % arr.length];
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
return acc;
},
['M', ...stroke[0], 'Q']
);
d.push('Z');
return d.join(' ');
}