Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions examples/tests/detect-touch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { Point } from '@lightningjs/renderer';
import type { ExampleSettings } from '../common/ExampleSettings.js';
import type { CoreNode } from '../../dist/src/core/CoreNode.js';

const getRandomValue = (min: number, max: number) => {
return Math.random() * (max - min) + min;
};

const getRandomColor = () => {
const randomInt = Math.floor(Math.random() * Math.pow(2, 24)); // Use 24 bits for RGB
const hexString = randomInt.toString(16).padStart(6, '0'); // RGB hex without alpha
return parseInt(hexString + 'FF', 16); // Append 'FF' for full alpha
};

const getRandomBezierCurve = () => {
// Generate random values for control points within specified ranges
const x1 = Math.random(); // 0 to 1
const y1 = Math.random() * 2; // Allow values above 1
const x2 = Math.random(); // 0 to 1
const y2 = Math.random() * 2 - 1; // Allow values between -1 and 1

// Return the Bezier curve in the required format
return `cubic-bezier(${x1.toFixed(2)}, ${y1.toFixed(2)}, ${x2.toFixed(
2,
)}, ${y2.toFixed(2)})`;
};

export default async function ({ renderer, testRoot }: ExampleSettings) {
const holder = renderer.createNode({
x: 0,
y: 0,
width: 1920,
height: 1080,
color: 0x000000ff,
parent: testRoot,
});

// Copy source texture from rootRenderToTextureNode
for (let i = 0; i < 50; i++) {
const dimension = getRandomValue(30, 150);
const node = renderer.createNode({
parent: holder,
x: getRandomValue(0, 1820),
y: getRandomValue(0, 980),
width: dimension,
height: dimension,
color: getRandomColor(),
interactive: true,
zIndex: getRandomValue(0, 100),
});

node
.animate(
{
x: getRandomValue(0, 1820),
y: getRandomValue(0, 980),
},
{
duration: getRandomValue(8000, 12000),
delay: getRandomValue(0, 5000),
stopMethod: 'reverse',
loop: true,
easing: getRandomBezierCurve(),
},
)
.start();
}

document.addEventListener('touchstart', (e: TouchEvent) => {
const { changedTouches } = e;
if (changedTouches.length) {
const touch = changedTouches.item(0);

const x = touch?.clientX ?? 0;
const y = touch?.clientY ?? 0;

const eventData: Point = {
x,
y,
};
// const nodes: CoreNode[] = renderer.stage.findNodesAtPoint(eventData);
const topNode: CoreNode | null =
renderer.stage.getNodeFromPosition(eventData);

if (topNode) {
topNode.scale = 1.5;
setTimeout(() => {
topNode.scale = 1;
}, 150);
}
}
});

document.addEventListener('mousemove', (e: MouseEvent) => {
const x = e?.clientX ?? 0;
const y = e?.clientY ?? 0;

const eventData: Point = {
x,
y,
};
// const nodes: CoreNode[] = renderer.stage.findNodesAtPoint(eventData);
const topNode: CoreNode | null =
renderer.stage.getNodeFromPosition(eventData);

if (topNode) {
topNode.scale = 1.5;
setTimeout(() => {
topNode.scale = 1;
}, 150);
}
});
}
19 changes: 19 additions & 0 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,12 @@ export interface CoreNodeProps {
* @default false
*/
strictBounds: boolean;
/**
* Mark the node as interactive so we can perform hit tests on it
* when pointer events are registered.
* @default false
*/
interactive?: boolean;
}

/**
Expand Down Expand Up @@ -775,6 +781,7 @@ export class CoreNode extends EventEmitter {
this.texture = props.texture;
this.src = props.src;
this.rtt = props.rtt;
this.interactive = props.interactive;

if (props.boundsMargin) {
this.boundsMargin = Array.isArray(props.boundsMargin)
Expand Down Expand Up @@ -2446,6 +2453,18 @@ export class CoreNode extends EventEmitter {
this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children;
}

set interactive(value: boolean | undefined) {
this.props.interactive = value;
// Update Stage's interactive Set
if (value === true) {
this.stage.interactiveNodes.add(this);
}
}

get interactive(): boolean | undefined {
return this.props.interactive;
}

animate(
props: Partial<CoreNodeAnimateProps>,
settings: Partial<AnimationSettings>,
Expand Down
51 changes: 50 additions & 1 deletion src/core/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js';
import { santizeCustomDataMap } from '../main-api/utils.js';
import type { SdfTextRenderer } from './text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js';
import type { CanvasTextRenderer } from './text-rendering/renderers/CanvasTextRenderer.js';
import { createBound, createPreloadBounds, type Bound } from './lib/utils.js';
import {
createBound,
createPreloadBounds,
pointInBound,
type Bound,
} from './lib/utils.js';
import type { Texture } from './textures/Texture.js';
import { ColorTexture } from './textures/ColorTexture.js';

Expand Down Expand Up @@ -91,6 +96,10 @@ export type StageFrameTickHandler = (
stage: Stage,
frameTickData: FrameTickPayload,
) => void;
export interface Point {
x: number;
y: number;
}

const bufferMemory = 2e6;
const autoStart = true;
Expand All @@ -105,6 +114,7 @@ export class Stage {
public readonly shManager: CoreShaderManager;
public readonly renderer: CoreRenderer;
public readonly root: CoreNode;
public readonly interactiveNodes: Set<CoreNode> = new Set();
public boundsMargin: [number, number, number, number];
public readonly defShaderCtr: BaseShaderController;
public readonly strictBound: Bound;
Expand Down Expand Up @@ -543,6 +553,44 @@ export class Stage {
this.renderRequested = true;
}

/**
* Find all nodes at a given point
* @param data
*/
findNodesAtPoint(data: Point): CoreNode[] {
const x = data.x / this.options.deviceLogicalPixelRatio;
const y = data.y / this.options.deviceLogicalPixelRatio;
const nodes: CoreNode[] = [];
for (const node of this.interactiveNodes) {
if (node.isRenderable === false) {
continue;
}
if (pointInBound(x, y, node.renderBound!) === true) {
nodes.push(node);
}
}
return nodes;
}

/**
* Find the top node at a given point
* @param data
* @returns
*/
getNodeFromPosition(data: Point): CoreNode | null {
const nodes: CoreNode[] = this.findNodesAtPoint(data);
if (nodes.length === 0) {
return null;
}
let topNode = nodes[0] as CoreNode;
for (let i = 0; i < nodes.length; i++) {
if (nodes[i]!.zIndex > topNode.zIndex) {
topNode = nodes[i]!;
}
}
return topNode || null;
}

/**
* Given a font name, and possible renderer override, return the best compatible text renderer.
*
Expand Down Expand Up @@ -761,6 +809,7 @@ export class Stage {
data: data,
preventCleanup: props.preventCleanup ?? false,
imageType: props.imageType,
interactive: props.interactive ?? false,
strictBounds: props.strictBounds ?? this.strictBounds,
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ export function boundLargeThanBound(bound1: Bound, bound2: Bound) {
);
}

export function pointInBound(x: number, y: number, bound: Bound) {
return !(x < bound.x1 || x > bound.x2 || y < bound.y1 || y > bound.y2);
}

export function isBoundPositive(bound: Bound): boolean {
return bound.x1 < bound.x2 && bound.y1 < bound.y2;
}
Expand Down