diff --git a/demo/effect-integrator.html b/demo/effect-integrator.html new file mode 100644 index 0000000..0927722 --- /dev/null +++ b/demo/effect-integrator.html @@ -0,0 +1,61 @@ + + + + + + Physics + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/EffectIntegrator.ts b/src/common/EffectIntegrator.ts new file mode 100644 index 0000000..f50e849 --- /dev/null +++ b/src/common/EffectIntegrator.ts @@ -0,0 +1,119 @@ +import type { FolkShape } from '../folk-shape'; + +/** + * Coordinates effects between multiple systems, integrating their proposals into a single result. + * Systems register, yield effects, and await integration when all systems are ready. + */ +export class EffectIntegrator { + private pending = new Map(); + private systems = new Set(); + private waiting = new Set(); + private resolvers: ((value: Map) => void)[] = []; + + /** Register a system to participate in effect integration */ + register(id: string) { + this.systems.add(id); + return { + yield: (element: Element, effect: T) => { + if (!this.pending.has(element)) { + this.pending.set(element, []); + } + (this.pending.get(element)! as T[]).push(effect); + }, + + /** Wait for all systems to submit effects, then receive integrated results */ + integrate: async (): Promise> => { + this.waiting.add(id); + + if (this.waiting.size === this.systems.size) { + // Last system to call integrate - do integration + for (const [element, effects] of this.pending) { + this.pending.set(element, (this.constructor as typeof EffectIntegrator).integrate(element, effects as T[])); + } + const results = this.pending as Map; + + // Reset for next frame + this.pending = new Map(); + this.waiting.clear(); + + // Resolve all waiting systems + this.resolvers.forEach((resolve) => resolve(results)); + this.resolvers = []; + + return results; + } + + // Not all systems ready - wait for integration + return new Promise((resolve) => { + this.resolvers.push(resolve); + }); + }, + }; + } + + /** Integrate multiple effects into a single result. Must be implemented by derived classes. */ + protected static integrate(element: Element, effects: any[]): any { + throw new Error('Derived class must implement static integrate'); + } +} + +// Transform-specific integrator +interface TransformEffect { + x: number; + y: number; + rotation: number; + width: number; + height: number; +} + +export class TransformIntegrator extends EffectIntegrator { + private static instance: TransformIntegrator; + + static register(id: string) { + if (!TransformIntegrator.instance) { + TransformIntegrator.instance = new TransformIntegrator(); + } + return TransformIntegrator.instance.register(id); + } + + /** If the element is focused, return the elements rect, otherwise average the effects */ + protected static override integrate(element: FolkShape, effects: TransformEffect[]): TransformEffect { + if (element === document.activeElement) { + // If the element is focused, we don't want to apply any effects and just return the current state + const rect = element.getTransformDOMRect(); + return { + x: rect.x, + y: rect.y, + rotation: rect.rotation, + width: rect.width, + height: rect.height, + }; + } + + // Accumulate all effects + const result = effects.reduce( + (acc, effect) => ({ + x: acc.x + effect.x, + y: acc.y + effect.y, + rotation: acc.rotation + effect.rotation, + width: effect.width, + height: effect.height, + }), + { x: 0, y: 0, rotation: 0, width: effects[0].width, height: effects[0].height } + ); + + // Average all effects + const count = effects.length; + result.x /= count; + result.y /= count; + result.rotation /= count; + + // Apply averaged results to element + element.x = result.x; + element.y = result.y; + element.rotation = result.rotation; + + // Return averaged result + return result; + } +} diff --git a/src/folk-base-set.ts b/src/folk-base-set.ts index 794cddf..6be36a0 100644 --- a/src/folk-base-set.ts +++ b/src/folk-base-set.ts @@ -63,12 +63,17 @@ export class FolkBaseSet extends FolkElement { #onSlotchange = () => this.#observeSources(); #observeSources() { + console.log('observeSources'); const childElements = new Set(this.children); const elements = this.sources ? document.querySelectorAll(this.sources) : []; const sourceElements = new Set(elements).union(childElements); const elementsToObserve = sourceElements.difference(this.sourceElements); const elementsToUnobserve = this.sourceElements.difference(sourceElements); + console.log('sourceElements', sourceElements); + console.log('elementsToObserve', elementsToObserve); + console.log('elementsToUnobserve', elementsToUnobserve); + this.unobserveSources(elementsToUnobserve); for (const el of elementsToObserve) { diff --git a/src/folk-graph.ts b/src/folk-graph.ts index 6c5e5e1..2e0a968 100644 --- a/src/folk-graph.ts +++ b/src/folk-graph.ts @@ -58,6 +58,8 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo const colaNodes = this.createNodes(); const colaLinks = this.createLinks(); + console.log(colaNodes, colaLinks); + this.graphSim.nodes(colaNodes).links(colaLinks).linkDistance(250).avoidOverlaps(true).handleDisconnected(true); }