diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/renderer/editor/library.tsx | 111 | ||||
-rw-r--r-- | src/renderer/editor/types.ts | 23 | ||||
-rw-r--r-- | src/renderer/editor/util.ts | 9 |
3 files changed, 90 insertions, 53 deletions
diff --git a/src/renderer/editor/library.tsx b/src/renderer/editor/library.tsx index 4b7dac7..840bdc9 100644 --- a/src/renderer/editor/library.tsx +++ b/src/renderer/editor/library.tsx @@ -1,46 +1,65 @@ import * as React from 'react'; +const { useCallback, useMemo } = React; import { makeStyles, Theme } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; import Container from '@material-ui/core/Container'; -import { Tiling } from './types'; -import { useReadFile } from './util'; +import * as Color from 'color'; +import * as glob from 'fast-glob'; + +import { Tiling, TilingMeta } from './types'; +import { usePromise, useReadFile, readJSON } from './util'; const useStyles = makeStyles((theme: Theme) => ({ - container: { + grid: { display: 'flex', flexWrap: 'wrap', + margin: theme.spacing(-0.5), }, tile: { imageRendering: 'pixelated', - border: `solid 1px ${theme.palette.divider}`, + borderStyle: 'solid', + borderWidth: 1, + borderColor: theme.palette.divider, background: theme.palette.background.paper, + position: 'relative', + width: 130, + height: 130, + margin: theme.spacing(0.5), + // cursor: 'pointer', + // '&:hover': { + // borderColor: theme.palette.text.secondary, + // boxShadow: `0 0 2px 1px ${theme.palette.text.secondary}`, + // }, + }, + img: { + position: 'absolute', + zIndex: 1, width: 128, height: 128, - margin: 4, + }, + label: { + position: 'absolute', + zIndex: 2, + left: 0, + right: 0, + bottom: 0, + height: '37.5%', + padding: theme.spacing(1), + background: Color(theme.palette.background.default) + .fade(0.3) + .string(), }, })); -const tilings: Tiling[] = [ - { - size: 1, - sprite: { name: 'dirt' }, - }, - { - size: 1, - sprite: { name: 'grass' }, - }, - { - size: 1, - sprite: { name: 'road_left' }, - }, - { - size: 1, - sprite: { name: 'road_right' }, - }, -]; +function tilingSprite(tiling: Tiling): string { + const x = (tiling.meta.width - 1) / 2; + const y = (tiling.meta.height - 1) / 2; + return `project/tiling/${tiling.id}/${x}_${y}.png`; +} interface TilingDisplayProps { tiling: Tiling; @@ -49,35 +68,53 @@ interface TilingDisplayProps { function TilingDisplay({ tiling }: TilingDisplayProps): JSX.Element | null { const classes = useStyles(); - let name: string; + const path = tilingSprite(tiling); + const image = useReadFile(path); + const src = useMemo(() => (image ? `data:image/png;base64,${image.toString('base64')}` : undefined), [image]); + return ( + <div className={classes.tile}> + <img className={classes.img} src={src} /> + <div className={classes.label}>{tiling.meta.name}</div> + </div> + ); +} - switch (tiling.size) { - case 1: - name = tiling.sprite.name; - break; - case 3: - name = tiling.sprites[1][1].name; - break; - } +async function listTilings(): Promise<string[]> { + const matches = await glob('project/tiling/*/meta.json'); + return matches.map((m) => m.split('/')[2]); +} - const path = `static/resources/sprite/tile/${name}.png`; - const image = useReadFile(path, 'base64'); +async function loadTilingMeta(id: string): Promise<TilingMeta> { + const path = `project/tiling/${id}/meta.json`; + const meta = await readJSON(path); + return meta as TilingMeta; +} - return <img src={image ? `data:image/png;base64,${image}` : undefined} className={classes.tile} />; +async function loadTilings(): Promise<Tiling[]> { + const tilings = await listTilings(); + return Promise.all( + tilings.map((id) => [id, loadTilingMeta(id)] as const).map(async ([id, p]) => ({ id, meta: await p })), + ); } export function Library(): JSX.Element { const classes = useStyles(); + const mkLoadTilings = useCallback(() => loadTilings(), []); + const tilings = usePromise(mkLoadTilings) ?? []; + return ( <Container> - <Box display='inline-block'> - <div className={classes.container}> + <Box mb={2}> + <div className={classes.grid}> {tilings.map((tiling, i) => ( <TilingDisplay key={i} tiling={tiling} /> ))} </div> </Box> + <Button variant='contained' color='primary'> + Add + </Button> </Container> ); } diff --git a/src/renderer/editor/types.ts b/src/renderer/editor/types.ts index 95e1bd2..4235bf4 100644 --- a/src/renderer/editor/types.ts +++ b/src/renderer/editor/types.ts @@ -1,16 +1,11 @@ -export type Tuple3<T> = [T, T, T]; -export type Grid3x3<T> = Tuple3<Tuple3<T>>; - -export type Sprite = { +export interface TilingMeta { name: string; -}; + desc?: string; + width: number; + height: number; +} -export type Tiling = - | { - size: 1; - sprite: Sprite; - } - | { - size: 3; - sprites: Grid3x3<Sprite>; - }; +export interface Tiling { + id: string; + meta: TilingMeta; +} diff --git a/src/renderer/editor/util.ts b/src/renderer/editor/util.ts index b2331bb..f816de4 100644 --- a/src/renderer/editor/util.ts +++ b/src/renderer/editor/util.ts @@ -25,7 +25,12 @@ export function usePromise<T>(f: () => Promise<T>): T | null { return value; } -export function useReadFile(path: string, encoding: BufferEncoding): string | null { - const readFile = useCallback(() => fs.promises.readFile(path, encoding), [path, encoding]); +export function useReadFile(path: string): Buffer | null { + const readFile = useCallback(() => fs.promises.readFile(path), [path]); return usePromise(readFile); } + +export async function readJSON(path: string): Promise<unknown> { + const content = await fs.promises.readFile(path, 'utf8'); + return JSON.parse(content); +} |