Add an optional second mandala circle (#24).

pull/63/head
Atul Varma 2021-03-28 08:27:57 -04:00
rodzic c76bcc2d5a
commit d742f4156a
1 zmienionych plików z 132 dodań i 50 usunięć

Wyświetl plik

@ -12,6 +12,7 @@ import {
SvgSymbolContent, SvgSymbolContent,
SvgSymbolContext, SvgSymbolContext,
SvgSymbolData, SvgSymbolData,
swapColors,
} from "../svg-symbol"; } from "../svg-symbol";
import { VocabularyWidget } from "../vocabulary-widget"; import { VocabularyWidget } from "../vocabulary-widget";
import { import {
@ -26,8 +27,39 @@ import { NumericRange, range } from "../util";
import { Random } from "../random"; import { Random } from "../random";
import { PointWithNormal } from "../specs"; import { PointWithNormal } from "../specs";
import { getAttachmentTransforms } from "../attach"; import { getAttachmentTransforms } from "../attach";
import { Checkbox } from "../checkbox";
const EYE = SvgVocabulary.get("eye_vertical"); const CIRCLE_1_DEFAULTS: MandalaCircleParams = {
data: SvgVocabulary.get("eye_vertical"),
radius: 50,
numSymbols: 6,
};
const CIRCLE_2_DEFAULTS: MandalaCircleParams = {
data: SvgVocabulary.get("leg"),
radius: 0,
numSymbols: 3,
};
const CIRCLE_2_DEFAULT_SCALE = 0.5;
const RADIUS: NumericRange = {
min: 0,
max: 1000,
step: 1,
};
const NUM_SYMBOLS: NumericRange = {
min: 1,
max: 30,
step: 1,
};
const SCALE: NumericRange = {
min: 0.1,
max: 2,
step: 0.1,
};
/** /**
* Returns the anchor point of the given symbol; if it doesn't have * Returns the anchor point of the given symbol; if it doesn't have
@ -44,13 +76,15 @@ function getAnchorOrCenter(symbol: SvgSymbolData): PointWithNormal {
); );
} }
const MandalaCircle: React.FC< type MandalaCircleParams = {
{ data: SvgSymbolData;
data: SvgSymbolData; radius: number;
radius: number; numSymbols: number;
numSymbols: number; };
} & SvgSymbolContext
> = (props) => { const MandalaCircle: React.FC<MandalaCircleParams & SvgSymbolContext> = (
props
) => {
const degreesPerItem = 360 / props.numSymbols; const degreesPerItem = 360 / props.numSymbols;
const { translation, rotation } = getAttachmentTransforms( const { translation, rotation } = getAttachmentTransforms(
{ {
@ -83,62 +117,110 @@ const MandalaCircle: React.FC<
return <>{symbols}</>; return <>{symbols}</>;
}; };
type NumericParams = NumericRange & { default: number }; const MandalaCircleParamsWidget: React.FC<{
idPrefix: string;
const RADIUS: NumericParams = { value: MandalaCircleParams;
min: 0, onChange: (value: MandalaCircleParams) => void;
max: 1000, }> = ({ idPrefix, value, onChange }) => {
step: 1, return (
default: 50, <div className="thingy">
<VocabularyWidget
id={`${idPrefix}symbol`}
label="Symbol"
value={value.data}
onChange={(data) => onChange({ ...value, data })}
choices={SvgVocabulary}
/>
<NumericSlider
id={`${idPrefix}radius`}
label="Radius"
value={value.radius}
onChange={(radius) => onChange({ ...value, radius })}
{...RADIUS}
/>
<NumericSlider
id={`${idPrefix}numSymbols`}
label="Number of symbols"
value={value.numSymbols}
onChange={(numSymbols) => onChange({ ...value, numSymbols })}
{...NUM_SYMBOLS}
/>
</div>
);
}; };
const NUM_SYMBOLS: NumericParams = { function getRandomCircleParams(rng: Random): MandalaCircleParams {
min: 1, return {
max: 30, data: rng.choice(SvgVocabulary.items),
step: 1, radius: rng.inRange(RADIUS),
default: 6, numSymbols: rng.inRange(NUM_SYMBOLS),
}; };
}
export const MandalaPage: React.FC<{}> = () => { export const MandalaPage: React.FC<{}> = () => {
const svgRef = useRef<SVGSVGElement>(null); const svgRef = useRef<SVGSVGElement>(null);
const [bgColor, setBgColor] = useState(DEFAULT_BG_COLOR); const [bgColor, setBgColor] = useState(DEFAULT_BG_COLOR);
const [symbol, setSymbol] = useState(EYE); const [circle1, setCircle1] = useState<MandalaCircleParams>(
CIRCLE_1_DEFAULTS
);
const [circle2, setCircle2] = useState<MandalaCircleParams>(
CIRCLE_2_DEFAULTS
);
const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext()); const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext());
const [radius, setRadius] = useState(RADIUS.default); const [useTwoCircles, setUseTwoCircles] = useState(false);
const [numSymbols, setNumSymbols] = useState(NUM_SYMBOLS.default); const [invertCircle2, setInvertCircle2] = useState(true);
const [circle2Scale, setCircle2Scale] = useState(CIRCLE_2_DEFAULT_SCALE);
const randomize = () => { const randomize = () => {
const rng = new Random(Date.now()); const rng = new Random(Date.now());
setRadius(rng.inRange(RADIUS)); setCircle1(getRandomCircleParams(rng));
setNumSymbols(rng.inRange(NUM_SYMBOLS)); setCircle2(getRandomCircleParams(rng));
setSymbol(rng.choice(SvgVocabulary.items)); setCircle2Scale(rng.inRange(SCALE));
}; };
const circle2SymbolCtx = invertCircle2 ? swapColors(symbolCtx) : symbolCtx;
return ( return (
<> <>
<h1>Mandala!</h1> <h1>Mandala!</h1>
<SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx}> <SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx}>
<ColorWidget label="Background" value={bgColor} onChange={setBgColor} />{" "} <ColorWidget label="Background" value={bgColor} onChange={setBgColor} />{" "}
</SymbolContextWidget> </SymbolContextWidget>
<fieldset>
<legend>First circle</legend>
<MandalaCircleParamsWidget
idPrefix="c1"
value={circle1}
onChange={setCircle1}
/>
</fieldset>
<div className="thingy"> <div className="thingy">
<VocabularyWidget <Checkbox
label="Symbol" label="Add a second circle"
value={symbol} value={useTwoCircles}
onChange={setSymbol} onChange={setUseTwoCircles}
choices={SvgVocabulary}
/>
<NumericSlider
label="Radius"
value={radius}
onChange={setRadius}
{...RADIUS}
/>
<NumericSlider
label="Numer of symbols"
value={numSymbols}
onChange={setNumSymbols}
{...NUM_SYMBOLS}
/> />
</div> </div>
{useTwoCircles && (
<fieldset>
<legend>Second circle</legend>
<MandalaCircleParamsWidget
idPrefix="c2"
value={circle2}
onChange={setCircle2}
/>
<NumericSlider
label="Scale"
value={circle2Scale}
onChange={setCircle2Scale}
{...SCALE}
/>
<Checkbox
label="Invert colors"
value={invertCircle2}
onChange={setInvertCircle2}
/>
</fieldset>
)}
<div className="thingy"> <div className="thingy">
<button accessKey="r" onClick={randomize}> <button accessKey="r" onClick={randomize}>
<u>R</u>andomize! <u>R</u>andomize!
@ -148,12 +230,12 @@ export const MandalaPage: React.FC<{}> = () => {
<HoverDebugHelper> <HoverDebugHelper>
<AutoSizingSvg padding={20} ref={svgRef} bgColor={bgColor}> <AutoSizingSvg padding={20} ref={svgRef} bgColor={bgColor}>
<SvgTransform transform={svgScale(0.5)}> <SvgTransform transform={svgScale(0.5)}>
<MandalaCircle <MandalaCircle {...circle1} {...symbolCtx} />
data={symbol} {useTwoCircles && (
radius={radius} <SvgTransform transform={svgScale(circle2Scale)}>
numSymbols={numSymbols} <MandalaCircle {...circle2} {...circle2SymbolCtx} />
{...symbolCtx} </SvgTransform>
/> )}
</SvgTransform> </SvgTransform>
</AutoSizingSvg> </AutoSizingSvg>
</HoverDebugHelper> </HoverDebugHelper>