Skip to content

Commit

Permalink
implement Vector control for 2d vector schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
emilwidlund committed Dec 28, 2024
1 parent 1258048 commit 925f6fb
Show file tree
Hide file tree
Showing 4 changed files with 6,311 additions and 4,653 deletions.
3 changes: 3 additions & 0 deletions apps/web/src/components/Controls/Control/Control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StringControl } from '../StringControl/StringControl';
import { ColorControl } from '../ColorControl/ColorControl';
import { BooleanControl } from '../BooleanControl/BooleanControl';
import { ColorSchemaType } from '@/utils';
import { VectorControl } from '../VectorControl/VectorControl';

export interface ControlProps {
port: Input | Output;
Expand Down Expand Up @@ -47,5 +48,7 @@ export const Control = observer(({ port, disabled, onBlur }: ControlProps) => {
onBlur={onBlur}
/>
);
case '2D Vector':
return <VectorControl port={port} disabled={disabled} />;
}
});
186 changes: 186 additions & 0 deletions apps/web/src/components/Controls/VectorControl/VectorControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Input, Output } from '@bitspace/circuit';
import { Vector2Schema } from '@bitspace/schemas';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { z } from 'zod';
import clsx from 'clsx';
import Draggable, { DraggableEventHandler } from 'react-draggable';

export interface VectorControlProps {
port:
| Input<z.infer<ReturnType<typeof Vector2Schema>>>
| Output<z.infer<ReturnType<typeof Vector2Schema>>>;
disabled?: boolean;
onBlur?: (value: z.infer<ReturnType<typeof Vector2Schema>>) => void;
}

const JOYSTICK_SIZE = 16;
const getSamplingSize = (size: number) => size;

export const VectorControl = observer(
({ port, disabled, onBlur }: VectorControlProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);

const [value, setValue] = useState<
z.infer<ReturnType<typeof Vector2Schema>>
>({ x: 0, y: 0 });

useEffect(() => {
const subscription = port.subscribe(value => {
setValue(value);
});

return () => {
subscription.unsubscribe();
};
}, [port]);

const handleChange = useCallback(
(position: { x: number; y: number }) => {
if (!disabled) {
port.next(position);
}
},
[port, disabled, onBlur]
);

const handleDrag: DraggableEventHandler = useCallback(
(_, data) => {
const rect = containerRef.current?.getBoundingClientRect();
const containerWidth = getSamplingSize(rect?.width ?? 0);
const containerHeight = getSamplingSize(rect?.height ?? 0);

const x = (data.x / containerWidth) * 2 - 1;
const y = 1 - (data.y / containerHeight) * 2;

handleChange({ x, y });
},
[handleChange, containerRef]
);

const absolutePosition = useMemo(() => {
if (!containerRef.current) return { x: 0, y: 0 };
const rect = containerRef.current.getBoundingClientRect();

const x = (value.x + 1) / 2;
const y = 1 - (value.y + 1) / 2;

const adjustedWidth = getSamplingSize(rect.width);
const adjustedHeight = getSamplingSize(rect.height);

return {
x: x * adjustedWidth,
y: y * adjustedHeight
};
}, [value, containerRef]);

useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
const ctx = canvas.getContext('2d');

if (!ctx) return;
const width = canvas.width;
const height = canvas.height;

ctx.clearRect(0, 0, width, height);

// Draw vertical line
ctx.beginPath();
ctx.moveTo(width / 2, 0);
ctx.lineTo(width / 2, height);
ctx.strokeStyle = 'rgba(226, 232, 240, 1)';
ctx.lineWidth = 1;
ctx.stroke();

// Draw horizontal line
ctx.beginPath();
ctx.moveTo(0, height / 2);
ctx.lineTo(width, height / 2);
ctx.strokeStyle = 'rgba(226, 232, 240, 1)';
ctx.lineWidth = 1;
ctx.stroke();
}
}, [value, containerRef]);

return (
<div className="flex flex-col gap-y-4">
<div className="flex flex-row gap-x-2">
{Object.entries(value).map(([key, val]) => (
<input
className={clsx(
'px-4 py-2 rounded-full w-full shadow-sm border border-slate-100 focus-visible:outline-slate-200',
{
'text-slate-400': disabled
}
)}
type="number"
step={0.01}
placeholder={port.type.description}
value={val}
disabled={disabled}
onChange={e => {
port.next({
...value,
[key]: parseFloat(e.target.value)
});
}}
/>
))}
</div>
<div
ref={containerRef}
className="w-full bg-slate-100 aspect-square rounded-2xl relative border border-slate-200 overflow-hidden"
style={{
backgroundImage:
'radial-gradient(rgba(203, 213, 225, 1) 6%, transparent 6%)',
backgroundPosition: '0 0',
backgroundSize: '25px 25px'
}}
>
<Draggable
axis="both"
onDrag={handleDrag}
bounds={{
left: 0,
top: 0,
right: containerRef.current?.clientWidth ?? 0,
bottom: containerRef.current?.clientHeight ?? 0
}}
position={{
x: absolutePosition.x,
y: absolutePosition.y
}}
positionOffset={{
x: -(JOYSTICK_SIZE / 2),
y: -(JOYSTICK_SIZE / 2)
}}
disabled={disabled}
>
<div
className={clsx(
'absolute flex items-center justify-center rounded-full bg-slate-300 cursor-pointer z-10',
{
'cursor-not-allowed': disabled
}
)}
style={{
width: JOYSTICK_SIZE,
height: JOYSTICK_SIZE
}}
>
<div className="w-1 h-1 bg-white rounded-full"></div>
</div>
</Draggable>
<canvas
ref={canvasRef}
className="absolute inset-0"
width={containerRef.current?.clientWidth ?? 0}
height={containerRef.current?.clientHeight ?? 0}
/>
</div>
</div>
);
}
);
4 changes: 2 additions & 2 deletions packages/nodes/src/primitives/Shader/Shader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ void main() {

const FRAGMENT_SHADER = `varying vec2 vUv;
uniform float time;
uniform vec3 offset;
uniform vec2 offset;
void main() {
float r = sin(time) * 0.5 + 0.5;
gl_FragColor = vec4(vUv * offset.xy, r, 1.0);
gl_FragColor = vec4(vUv * (offset.xy + vec2(1.0)), r, 1.0);
}`;

export const ShaderSchema = () =>
Expand Down
Loading

0 comments on commit 925f6fb

Please sign in to comment.