import React, { useRef, useState } from "react";
import { AutoSizingSvg } from "../auto-sizing-svg";
import { ExportWidget } from "../export-svg";
import { HoverDebugHelper } from "../hover-debug-helper";
import { NumericSlider } from "../numeric-slider";
import {
noFillIfShowingSpecs,
SvgSymbolContext,
swapColors,
} from "../svg-symbol";
import { VocabularyWidget } from "../vocabulary-widget";
import { svgRotate, svgScale, SvgTransform } from "../svg-transform";
import { SvgVocabulary } from "../svg-vocabulary";
import { isEvenNumber, NumericRange } from "../util";
import { Random } from "../random";
import { Checkbox } from "../checkbox";
import {
CompositionContextWidget,
createSvgCompositionContext,
} from "../svg-composition-context";
import { Page } from "../page";
import { MandalaCircle, MandalaCircleParams } from "../mandala-circle";
import { useAnimationPct } from "../animation";
type ExtendedMandalaCircleParams = MandalaCircleParams & {
scaling: number;
rotation: number;
symbolScaling: number;
symbolRotation: number;
animateSymbolRotation: boolean;
};
const CIRCLE_1_DEFAULTS: ExtendedMandalaCircleParams = {
data: SvgVocabulary.get("eye"),
radius: 300,
numSymbols: 5,
scaling: 1,
rotation: 0,
symbolScaling: 1,
symbolRotation: 0,
invertEveryOtherSymbol: false,
animateSymbolRotation: false,
};
const CIRCLE_2_DEFAULTS: ExtendedMandalaCircleParams = {
data: SvgVocabulary.get("leg"),
radius: 0,
numSymbols: 3,
scaling: 0.5,
rotation: 0,
symbolScaling: 1,
symbolRotation: 0,
invertEveryOtherSymbol: false,
animateSymbolRotation: false,
};
const RADIUS: NumericRange = {
min: -500,
max: 500,
step: 1,
};
const NUM_SYMBOLS: NumericRange = {
min: 1,
max: 20,
step: 1,
};
const SCALING: NumericRange = {
min: 0.1,
max: 1,
step: 0.05,
};
const ROTATION: NumericRange = {
min: 0,
max: 359,
step: 1,
};
const DURATION_SECS: NumericRange = {
min: 0.5,
max: 10,
step: 0.1,
};
const DEFAULT_DURATION_SECS = 3;
const ExtendedMandalaCircle: React.FC<
ExtendedMandalaCircleParams & SvgSymbolContext
> = ({ scaling, rotation, symbolScaling, symbolRotation, ...props }) => {
props = {
...props,
symbolTransforms: [svgScale(symbolScaling), svgRotate(symbolRotation)],
};
return (
);
};
function animateMandalaCircleParams(
value: ExtendedMandalaCircleParams,
animPct: number
): ExtendedMandalaCircleParams {
if (value.animateSymbolRotation) {
value = {
...value,
symbolRotation: animPct * ROTATION.max,
};
}
return value;
}
function isAnyMandalaCircleAnimated(
values: ExtendedMandalaCircleParams[]
): boolean {
return values.some((value) => value.animateSymbolRotation);
}
const ExtendedMandalaCircleParamsWidget: React.FC<{
idPrefix: string;
value: ExtendedMandalaCircleParams;
onChange: (value: ExtendedMandalaCircleParams) => void;
}> = ({ idPrefix, value, onChange }) => {
return (
onChange({ ...value, data })}
choices={SvgVocabulary}
/>
onChange({ ...value, radius })}
{...RADIUS}
/>
onChange({ ...value, numSymbols })}
{...NUM_SYMBOLS}
/>
onChange({ ...value, scaling })}
{...SCALING}
/>
onChange({ ...value, rotation })}
{...ROTATION}
/>
onChange({ ...value, symbolScaling })}
{...SCALING}
/>
onChange({ ...value, symbolRotation })}
{...ROTATION}
/>
onChange({ ...value, animateSymbolRotation })
}
/>
onChange({ ...value, invertEveryOtherSymbol })
}
/>
);
};
function getRandomCircleParams(rng: Random): MandalaCircleParams {
return {
data: rng.choice(SvgVocabulary.items),
radius: rng.inRange(RADIUS),
numSymbols: rng.inRange(NUM_SYMBOLS),
invertEveryOtherSymbol: rng.bool(),
};
}
export const MandalaPage: React.FC<{}> = () => {
const svgRef = useRef(null);
const canvasRef = useRef(null);
const [circle1, setCircle1] = useState(CIRCLE_1_DEFAULTS);
const [circle2, setCircle2] = useState(CIRCLE_2_DEFAULTS);
const [durationSecs, setDurationSecs] = useState(DEFAULT_DURATION_SECS);
const [baseCompCtx, setBaseCompCtx] = useState(createSvgCompositionContext());
const [useTwoCircles, setUseTwoCircles] = useState(false);
const [invertCircle2, setInvertCircle2] = useState(true);
const [firstBehindSecond, setFirstBehindSecond] = useState(false);
const randomize = () => {
const rng = new Random(Date.now());
setCircle1({ ...circle1, ...getRandomCircleParams(rng) });
setCircle2({ ...circle2, ...getRandomCircleParams(rng) });
};
const isAnimated = isAnyMandalaCircleAnimated([circle1, circle2]);
const animPct = useAnimationPct(isAnimated ? durationSecs * 1000 : 0);
const symbolCtx = noFillIfShowingSpecs(baseCompCtx);
const circle2SymbolCtx = invertCircle2 ? swapColors(symbolCtx) : symbolCtx;
const circles = [
,
];
if (useTwoCircles) {
circles.push(
);
if (firstBehindSecond) {
circles.reverse();
}
}
return (
{useTwoCircles && (
)}
{isAnimated && (
setDurationSecs(duration)}
{...DURATION_SECS}
/>
)}
{" "}
);
};