Skip to content

Commit

Permalink
feat: refactor done
Browse files Browse the repository at this point in the history
  • Loading branch information
birkir committed Aug 28, 2020
1 parent b40ce13 commit 936485d
Show file tree
Hide file tree
Showing 31 changed files with 1,182 additions and 840 deletions.
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,39 @@ https://codesandbox.io/s/react-three-fiber-gui-62pvp
Basic example

```tsx
import { Controls, useControl } from 'react-three-gui';
import { ControlsProvider, Controls, useControl } from 'react-three-gui';

export const App = () => {
const rotationX = useControl('Rotation X', { type: 'number' });
return (
<>
<Canvas>
<mesh rotation-x={rotationX} />
</Canvas>
<Controls />
</>
);
<ControlsProvider>
<Canvas>
<mesh rotation-x={rotationX} />
</Canvas>
<Controls />
</ControlsProvider>
);
};
```

Use the spring option to return a react-spring value:

```tsx
useControl('My ctrl', {
type: 'number',
spring: true,
})
});

// or pass a react-spring configuration value

useControl('My ctrl', {
type: 'number',
spring: { mass: 5, tension: 280, friction: 50 },
})
});
```

Also possible to pass in your own state:

```tsx
const [value, set] = useState(0);

Expand All @@ -54,11 +56,12 @@ useControl('Adjust value', {
```

Also you can pass your own control component:

```tsx
const MyControl = ({ control, value }) => (
const MyControl = ({ value, setValue }) => (
<input
type="number"
onChange={e => control.set(e.currentTarget.value)}
onChange={e => setValue(e.currentTarget.value)}
value={value}
/>
);
Expand All @@ -71,13 +74,14 @@ useControl('Test', {
```

## API

```tsx
import { useControl, Controls } from 'react-three-gui';

// All the possible options
useControl(name: string, {
// General
type: 'number' | 'xypad' | 'boolean' | 'button' | 'color' | 'select' | 'string' | 'custom';
type: 'number' | 'xypad' | 'boolean' | 'button' | 'color' | 'select' | 'string' | 'file' | 'custom';
value: any; // Initial value
spring: boolean | SpringConfig; // Use spring
group: string; // Group name
Expand All @@ -96,12 +100,19 @@ useControl(name: string, {
// button
onClick(): void;

// file
loader?: THREE.TextureLoader | THREE.FileLoader | etc;

// custom
component?: React.Component;
});

// Currently does not have any props
<Controls />
// Controls component
<Controls
title="Custom title"
collapsed={true}
defaultClosedGroups={['Other', 'Stuff']}
/>
```

## Supported controls
Expand All @@ -118,14 +129,17 @@ useControl(name: string, {
- Returns `string` (as hex: #ffffff)
- select
- Returns `string`
- file
- Returns `new THREE.FileLoader`
- string
- Returns `string`

### Future plans

- [x] Support custom control components
- [ ] Support passing refs and directly manipulate THREE objects
- [x] File upload loader control
- [x] Groups
- [x] Draggable Widget
- [ ] Collapsable widget
- [x] Collapsable widget
- [x] Persist state localstorage
- [ ] Multi platform?
138 changes: 80 additions & 58 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,70 @@ import * as React from 'react';
import 'react-app-polyfill/ie11';
import * as ReactDOM from 'react-dom';
import { animated } from 'react-spring';
import { a } from 'react-spring/three';
import { Canvas } from 'react-three-fiber';
import { a } from '@react-spring/three';
import { Canvas, useLoader } from 'react-three-fiber';
import * as THREE from 'three';
import { Controls, useControl } from '../src';
import { Controls, ControlsProvider, useControl, BaseControl } from '../src';
import fontFile from './resources/unknown';
import { useEffect } from 'react';

function Text({ children, size = 1, letterSpacing = 0.01, color = '#000000' }) {
const [font] = React.useState(() => new THREE.FontLoader().parse(fontFile))
const [font] = React.useState(() => new THREE.FontLoader().parse(fontFile));
const [shapes, [x, y]] = React.useMemo(() => {
let x = 0,
y = 0
let letters = [...children]
let mat = new THREE.MeshBasicMaterial({ color, opacity: 1, transparent: true })
y = 0;
let letters = [...children];
let mat = new THREE.MeshBasicMaterial({
color,
opacity: 1,
transparent: true,
});
return [
letters.map(letter => {
const geom = new THREE.ShapeGeometry(font.generateShapes(letter, size, 1))
geom.computeBoundingBox()
const mesh = new THREE.Mesh(geom, mat)
mesh.position.x = x
x += geom.boundingBox.max.x + letterSpacing
y = Math.max(y, geom.boundingBox.max.y)
return mesh
const geom = new THREE.ShapeGeometry(font.generateShapes(letter, size));
geom.computeBoundingBox();
const mesh = new THREE.Mesh(geom, mat);
mesh.position.x = x;
x += geom.boundingBox?.max?.x! + letterSpacing;
y = Math.max(y, geom.boundingBox?.max?.y!);
return mesh;
}),
[x, y],
]
}, [children])
];
}, [children]);

return (
<group position={[-x / 2, -y / 2, 0]}>
{shapes.map((shape, index) => (
<primitive key={index} object={shape} />
))}
</group>
)
);
}

const Next = () => {
const rotationX = useControl('Mega', { group: 'Test', type: 'number', spring: true });
const rotationX = useControl('Mega', {
group: 'Test',
type: 'number',
spring: true,
});
return (
<a.mesh position={[1.5, 0, 0]} rotation-x={rotationX}>
<boxGeometry attach="geometry" args={[1, 1, 1]} />
<meshStandardMaterial attach="material" />
</a.mesh>
)
);
};

const Box = () => {
const rotationX = useControl('Rotate X', { group: 'Basic', type: 'number', spring: true });
const ref = React.useRef<THREE.Mesh>();

const rotationX = useControl('Rotate X', {
group: 'Basic',
type: 'number',
spring: true,
});

const rotationY = useControl('Rotate Y', {
type: 'number',
group: 'Basic',
Expand All @@ -62,6 +78,11 @@ const Box = () => {
mass: 2,
},
});
const bool = useControl('Boolean', {
group: 'More',
type: 'boolean',
});

const color = useControl('Material color', {
type: 'color',
group: 'Basic',
Expand All @@ -72,14 +93,10 @@ const Box = () => {
value: { x: 0, y: 0 },
distance: Math.PI,
});
const bool = useControl('Allowed', {
group: 'More',
type: 'boolean',
});
const dropdown = useControl('Pick one', {
group: 'More',
type: 'select',
items: ['foo', 'bar', 'baz']
items: ['foo', 'bar', 'baz'],
});
const str = useControl('Text', {
group: 'More',
Expand All @@ -91,27 +108,22 @@ const Box = () => {
type: 'button',
onClick() {
alert('Hello world');
}
},
});

const MyControl = ({ control, value }) => (
<label>Test:
<input
type="number"
onChange={e => control.set(e.currentTarget.value)}
value={value}
/>
</label>
);

const size = useControl('Test', {
const texture = useControl('Texture', {
group: 'More',
type: 'custom',
value: 1,
component: MyControl
type: 'file',
value: undefined,
loader: new THREE.TextureLoader(),
});

const ref = React.useRef<THREE.Mesh>();
useEffect(() => {
if (ref.current) {
(ref.current.material as THREE.Material).needsUpdate = true;
}
}, [texture]);

return (
<>
<a.mesh
Expand All @@ -120,38 +132,48 @@ const Box = () => {
rotation-x={rotationX}
rotation-y={rotationY}
>
<boxGeometry attach="geometry" args={[size, size, size]} />
<a.meshStandardMaterial attach="material"
color={color}
/>
<boxGeometry attach="geometry" args={[1, 1, 1]} />
<a.meshPhongMaterial attach="material" map={texture} color={color} />
</a.mesh>
<Text>{str}</Text>
{dropdown === 'bar' && <Next />}
</>
)
}
);
};

const Hello = () => {
useControl('1', { type: 'number' });
useControl('2', { type: 'number', max: 10 });
useControl('3', { type: 'number', min: -5, max: 5, value: -2.5 });
useControl('4', { type: 'number', min: 0, max: 200, value: 100 });
useControl('5', { type: 'number', scrub: true });
useControl('5', { type: 'number', scrub: true, distance: 1000 });
return (<animated.div style={{ width: 100, height: 100, background: 'red' }} />)
const a1 = useControl('1', { type: 'number' });
const a2 = useControl('2', { type: 'number', max: 10 });
const a3 = useControl('3', { type: 'number', min: -5, max: 5, value: -2.5 });
const a4 = useControl('4', { type: 'number', min: 0, max: 200, value: 100 });
const a5 = useControl('5', { type: 'number', scrub: true });
const a6 = useControl('6', { type: 'number', scrub: true, distance: 1000 });
return (
<animated.div style={{ width: 180, background: 'orange', padding: 20 }}>
<p>This is a div</p>
<div>1: {a1}</div>
<div>2: {a2}</div>
<div>3: {a3}</div>
<div>4: {a4}</div>
<div>5: {a5}</div>
<div>6: {a6}</div>
</animated.div>
);
};

const App = () => {
return (
<div>
<ControlsProvider>
<Canvas style={{ width: 800, height: 600 }}>
<ambientLight intensity={1} />
<pointLight position={[0, 2, 2]} />
<Box />
<React.Suspense fallback={null}>
<Box />
</React.Suspense>
</Canvas>
<Controls />
<Hello />
</div>
<Controls />
</ControlsProvider>
);
};

Expand Down
16 changes: 10 additions & 6 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@
"build": "parcel build index.html"
},
"dependencies": {
"react-app-polyfill": "^1.0.0",
"react": ">= 16.8.0",
"react-app-polyfill": "^1.0.6",
"react-dom": ">= 16.8.0",
"react-spring": "^8.0.27",
"react-three-fiber": "^4.2.20",
"three": "^0.119.1",
"three": "^0.120.0",
"trim-right": "^1.0.1"
},
"alias": {
"react": "../node_modules/react",
"react-dom": "../node_modules/react-dom/profiling",
"scheduler/tracing": "../node_modules/scheduler/tracing-profiling",
"react-spring": "../node_modules/react-spring"
"@react-spring/web": "../node_modules/@react-spring/web",
"@react-spring/three": "../node_modules/@react-spring/three"
},
"devDependencies": {
"@types/react": "^16.8.15",
"@types/react-dom": "^16.8.4",
"@types/react": "^16.9.48",
"@types/react-dom": "^16.9.8",
"parcel": "^1.12.4",
"typescript": "^3.9.7"
"typescript": "^4.0.2"
}
}
Loading

0 comments on commit 936485d

Please sign in to comment.