Add an optional second mandala circle (#24).
rodzic
c76bcc2d5a
commit
d742f4156a
|
@ -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>
|
||||||
|
|
Ładowanie…
Reference in New Issue