Add a very basic Mandala page. (#57)

This adds an extremely simple Mandala page (for #24) with a single circle Mandala comprised of several eyes.  The symbol style is configurable, but parameters for the actual Mandala are not (yet).

Doing this also involved factoring out a `<SvgTransforms>` component, which makes setting up SVG transforms a bit easier.

Also moved `getSymbol` of `creature-page.tsx` and into `svg-vocabulary.tsx`, with the new name `getSvgSymbol`.
pull/58/head
Atul Varma 2021-03-26 18:07:01 -04:00 zatwierdzone przez GitHub
rodzic 7d62a5b7f6
commit f790838b06
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
7 zmienionych plików z 213 dodań i 42 usunięć

Wyświetl plik

@ -3,11 +3,13 @@ import ReactDOM from "react-dom";
import { WavesPage } from "./pages/waves-page";
import { VocabularyPage } from "./pages/vocabulary-page";
import { CreaturePage } from "./pages/creature-page";
import { MandalaPage } from "./pages/mandala-page";
const Pages = {
vocabulary: VocabularyPage,
creature: CreaturePage,
waves: WavesPage,
mandala: MandalaPage,
};
type PageName = keyof typeof Pages;

Wyświetl plik

@ -11,6 +11,13 @@ import {
SvgSymbolData,
swapColors,
} from "./svg-symbol";
import {
svgRotate,
svgScale,
svgTransformOrigin,
SvgTransforms,
svgTranslate,
} from "./svg-transform";
const DEFAULT_ATTACHMENT_SCALE = 0.5;
@ -111,27 +118,17 @@ type AttachmentTransformProps = {
};
const AttachmentTransform: React.FC<AttachmentTransformProps> = (props) => (
<g transform={`translate(${props.translate.x} ${props.translate.y})`}>
{/**
* We originally used "transform-origin" here but that's not currently
* supported by Safari. Instead, we'll set the origin of our symbol to
* the transform origin, do the transform, and then move our origin back to
* the original origin, which is equivalent to setting "transform-origin".
**/}
<g
transform={`translate(${props.transformOrigin.x} ${props.transformOrigin.y})`}
>
<g
transform={`scale(${props.scale.x} ${props.scale.y}) rotate(${props.rotate})`}
>
<g
transform={`translate(-${props.transformOrigin.x} -${props.transformOrigin.y})`}
>
{props.children}
</g>
</g>
</g>
</g>
<SvgTransforms
transforms={[
svgTranslate(props.translate),
svgTransformOrigin(props.transformOrigin, [
svgScale(props.scale),
svgRotate(props.rotate),
]),
]}
>
{props.children}
</SvgTransforms>
);
const AttachedCreatureSymbol: React.FC<AttachedCreatureSymbolProps> = ({

Wyświetl plik

@ -1,5 +1,5 @@
import React, { useContext, useRef, useState } from "react";
import { SvgVocabulary } from "../svg-vocabulary";
import { getSvgSymbol, SvgVocabulary } from "../svg-vocabulary";
import { createSvgSymbolContext, SvgSymbolData } from "../svg-symbol";
import {
AttachmentPointType,
@ -26,13 +26,6 @@ import { HoverDebugHelper } from "../hover-debug-helper";
const DEFAULT_BG_COLOR = "#858585";
/**
* Mapping from symbol names to symbol data, for quick and easy access.
*/
const SYMBOL_MAP = new Map(
SvgVocabulary.map((symbol) => [symbol.name, symbol])
);
/** Symbols that can be the "root" (i.e., main body) of a creature. */
const ROOT_SYMBOLS = SvgVocabulary.filter(
(data) => data.meta?.always_be_nested !== true
@ -82,18 +75,6 @@ const NESTED_SYMBOLS = SvgVocabulary.filter(
data.meta?.always_nest !== true && data.meta?.never_be_nested !== true
);
/**
* Returns the data for the given symbol, throwing an error
* if it doesn't exist.
*/
function getSymbol(name: string): SvgSymbolData {
const symbol = SYMBOL_MAP.get(name);
if (!symbol) {
throw new Error(`Unable to find the symbol "${name}"!`);
}
return symbol;
}
/**
* Given a parent symbol, return an array of random children to be nested within
* it.
@ -165,7 +146,7 @@ function getSymbolWithAttachments(
return result;
}
const symbol = createCreatureSymbolFactory(getSymbol);
const symbol = createCreatureSymbolFactory(getSvgSymbol);
const Eye = symbol("eye");

Wyświetl plik

@ -0,0 +1,76 @@
import React, { useState } from "react";
import { AutoSizingSvg } from "../auto-sizing-svg";
import { getBoundingBoxCenter } from "../bounding-box";
import { HoverDebugHelper } from "../hover-debug-helper";
import { reversePoint } from "../point";
import {
createSvgSymbolContext,
SvgSymbolContent,
SvgSymbolContext,
SvgSymbolData,
} from "../svg-symbol";
import {
svgRotate,
svgScale,
SvgTransforms,
svgTranslate,
} from "../svg-transform";
import { getSvgSymbol } from "../svg-vocabulary";
import { SymbolContextWidget } from "../symbol-context-widget";
import { range } from "../util";
const EYE = getSvgSymbol("eye");
const MandalaCircle: React.FC<
{
data: SvgSymbolData;
radius: number;
numSymbols: number;
} & SvgSymbolContext
> = (props) => {
const center = getBoundingBoxCenter(props.data.bbox);
const degreesPerItem = 360 / props.numSymbols;
const symbol = (
<SvgTransforms
transforms={[
svgTranslate({ x: props.radius, y: 0 }),
svgTranslate(reversePoint(center)),
]}
>
<SvgSymbolContent {...props} />
</SvgTransforms>
);
const symbols = range(props.numSymbols).map((i) => (
<SvgTransforms
key={i}
transforms={[svgRotate(degreesPerItem * i)]}
children={symbol}
/>
));
return <>{symbols}</>;
};
export const MandalaPage: React.FC<{}> = () => {
const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext());
return (
<>
<h1>Mandala!</h1>
<SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx} />
<HoverDebugHelper>
<AutoSizingSvg padding={20}>
<SvgTransforms transforms={[svgScale(0.5)]}>
<MandalaCircle
data={EYE}
radius={400}
numSymbols={6}
{...symbolCtx}
/>
</SvgTransforms>
</AutoSizingSvg>
</HoverDebugHelper>
</>
);
};

Wyświetl plik

@ -1,5 +1,10 @@
import { Point } from "../vendor/bezier-js";
/** Return the "reverse" of the given point/vector, i.e. scale it by -1. */
export function reversePoint(p: Point): Point {
return { x: -p.x, y: -p.y };
}
export function scalePointXY(p: Point, xScale: number, yScale: number): Point {
return {
x: p.x * xScale,

Wyświetl plik

@ -0,0 +1,91 @@
import React from "react";
import { Point } from "../vendor/bezier-js";
import { reversePoint } from "./point";
type SvgTransform =
| {
kind: "translate";
amount: Point;
}
| {
kind: "rotate";
degrees: number;
}
| {
kind: "scale";
amount: Point;
}
| {
kind: "transformOrigin";
amount: Point;
transforms: SvgTransform[];
};
function getSvgCodeForTransform(t: SvgTransform): string {
switch (t.kind) {
case "translate":
return `translate(${t.amount.x} ${t.amount.y})`;
case "scale":
return `scale(${t.amount.x} ${t.amount.y})`;
case "rotate":
return `rotate(${t.degrees})`;
case "transformOrigin":
/**
* We originally used the SVG "transform-origin" attribute here but
* that's not currently supported by Safari. Instead, we'll set the origin
* of our SVG to the transform origin, do the transform, and then move our
* origin back to the original origin, which does the same thing.
**/
return getSvgCodeForTransforms([
svgTranslate(t.amount),
...t.transforms,
svgTranslate(reversePoint(t.amount)),
]);
}
}
function getSvgCodeForTransforms(transforms: SvgTransform[]): string {
return transforms.map(getSvgCodeForTransform).join(" ");
}
/**
* Apply the given SVG transforms (e.g. rotate, scale)
* centered at the given origin point.
*/
export function svgTransformOrigin(
amount: Point,
transforms: SvgTransform[]
): SvgTransform {
return { kind: "transformOrigin", amount, transforms };
}
export function svgTranslate(amount: Point): SvgTransform {
return { kind: "translate", amount };
}
export function svgScale(amount: Point | number): SvgTransform {
if (typeof amount === "number") {
amount = { x: amount, y: amount };
}
return { kind: "scale", amount };
}
export function svgRotate(degrees: number): SvgTransform {
return { kind: "rotate", degrees };
}
/**
* Creates a SVG `<g>` element with the given children and transforms.
*
* Like the SVG `transform` attribute, the transforms are applied in
* the *reverse* order that they are specified.
*/
export const SvgTransforms: React.FC<{
transforms: SvgTransform[];
children: any;
}> = ({ transforms, children }) => {
return <g transform={getSvgCodeForTransforms(transforms)}>{children}</g>;
};

Wyświetl plik

@ -2,3 +2,22 @@ import type { SvgSymbolData } from "./svg-symbol";
import _SvgVocabulary from "./_svg-vocabulary.json";
export const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
/**
* Mapping from symbol names to symbol data, for quick and easy access.
*/
const SYMBOL_MAP = new Map(
SvgVocabulary.map((symbol) => [symbol.name, symbol])
);
/**
* Returns the data for the given symbol, throwing an error
* if it doesn't exist.
*/
export function getSvgSymbol(name: string): SvgSymbolData {
const symbol = SYMBOL_MAP.get(name);
if (!symbol) {
throw new Error(`Unable to find the symbol "${name}"!`);
}
return symbol;
}