Factor out a SvgCompositionContext. (#72)
This fixes #67 by making the background color selection/randomization code more DRY via the addition of a new `SvgCompositionContext` and a `CompositionContextWidget`. It also documents the `SvgSymbolContext` type, and moves the "randomize colors" button closer to the actual colors.pull/74/head
rodzic
7c05f78fd9
commit
e48b5f9bae
|
@ -71,6 +71,12 @@ ul.navbar li:last-child {
|
|||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.mandala-container label.checkbox {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mandala-container .color-widget {
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ type CheckboxProps = {
|
|||
|
||||
export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
||||
return (
|
||||
<label>
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.value}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import React, { useContext, useRef, useState } from "react";
|
||||
import { SvgVocabulary } from "../svg-vocabulary";
|
||||
import {
|
||||
createSvgSymbolContext,
|
||||
noFillIfShowingSpecs,
|
||||
SvgSymbolData,
|
||||
} from "../svg-symbol";
|
||||
import { noFillIfShowingSpecs, SvgSymbolData } from "../svg-symbol";
|
||||
import {
|
||||
AttachmentPointType,
|
||||
ATTACHMENT_POINT_TYPES,
|
||||
iterAttachmentPoints,
|
||||
} from "../specs";
|
||||
import { Random } from "../random";
|
||||
import { SymbolContextWidget } from "../symbol-context-widget";
|
||||
import { range } from "../util";
|
||||
|
||||
import { AutoSizingSvg } from "../auto-sizing-svg";
|
||||
|
@ -24,11 +19,12 @@ import {
|
|||
} from "../creature-symbol";
|
||||
import { HoverDebugHelper } from "../hover-debug-helper";
|
||||
import { svgScale, SvgTransform } from "../svg-transform";
|
||||
import { ColorWidget } from "../color-widget";
|
||||
import { NumericSlider } from "../numeric-slider";
|
||||
import { DEFAULT_BG_COLOR } from "../colors";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { createRandomColorPalette } from "../random-colors";
|
||||
import {
|
||||
CompositionContextWidget,
|
||||
createSvgCompositionContext,
|
||||
} from "../svg-composition-context";
|
||||
|
||||
/** Symbols that can be the "root" (i.e., main body) of a creature. */
|
||||
const ROOT_SYMBOLS = SvgVocabulary.items.filter(
|
||||
|
@ -178,38 +174,26 @@ function getDownloadBasename(randomSeed: number) {
|
|||
|
||||
export const CreaturePage: React.FC<{}> = () => {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const [bgColor, setBgColor] = useState(DEFAULT_BG_COLOR);
|
||||
const [randomSeed, setRandomSeed] = useState<number>(Date.now());
|
||||
const [randomlyInvert, setRandomlyInvert] = useState(true);
|
||||
const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext());
|
||||
const [compCtx, setCompCtx] = useState(createSvgCompositionContext());
|
||||
const [complexity, setComplexity] = useState(INITIAL_COMPLEXITY_LEVEL);
|
||||
const defaultCtx = useContext(CreatureContext);
|
||||
const newRandomSeed = () => setRandomSeed(Date.now());
|
||||
const ctx: CreatureContextType = noFillIfShowingSpecs({
|
||||
...defaultCtx,
|
||||
...symbolCtx,
|
||||
...compCtx,
|
||||
});
|
||||
const creature = COMPLEXITY_LEVEL_GENERATORS[complexity]({
|
||||
rng: new Random(randomSeed),
|
||||
randomlyInvert,
|
||||
});
|
||||
const randomizeColors = () => {
|
||||
const [bgColor, stroke, fill] = createRandomColorPalette(3);
|
||||
setBgColor(bgColor);
|
||||
setSymbolCtx({ ...symbolCtx, stroke, fill });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Creature!</h1>
|
||||
<SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx}>
|
||||
<ColorWidget label="Background" value={bgColor} onChange={setBgColor} />{" "}
|
||||
</SymbolContextWidget>
|
||||
<div className="thingy">
|
||||
<button accessKey="c" onClick={randomizeColors}>
|
||||
Randomize <u>c</u>olors!
|
||||
</button>
|
||||
</div>
|
||||
<CompositionContextWidget ctx={compCtx} onChange={setCompCtx} />
|
||||
<div className="thingy"></div>
|
||||
<div className="thingy">
|
||||
<NumericSlider
|
||||
label="Random creature complexity"
|
||||
|
@ -242,7 +226,7 @@ export const CreaturePage: React.FC<{}> = () => {
|
|||
</div>
|
||||
<CreatureContext.Provider value={ctx}>
|
||||
<HoverDebugHelper>
|
||||
<AutoSizingSvg padding={20} ref={svgRef} bgColor={bgColor}>
|
||||
<AutoSizingSvg padding={20} ref={svgRef} bgColor={compCtx.background}>
|
||||
<SvgTransform transform={svgScale(0.5)}>
|
||||
<CreatureSymbol {...creature} />
|
||||
</SvgTransform>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { AutoSizingSvg } from "../auto-sizing-svg";
|
||||
import { getBoundingBoxCenter } from "../bounding-box";
|
||||
import { ColorWidget } from "../color-widget";
|
||||
import { DEFAULT_BG_COLOR } from "../colors";
|
||||
import { ExportWidget } from "../export-svg";
|
||||
import { HoverDebugHelper } from "../hover-debug-helper";
|
||||
import { NumericSlider } from "../numeric-slider";
|
||||
import {
|
||||
createSvgSymbolContext,
|
||||
noFillIfShowingSpecs,
|
||||
safeGetAttachmentPoint,
|
||||
SvgSymbolContent,
|
||||
|
@ -23,13 +20,15 @@ import {
|
|||
svgTranslate,
|
||||
} from "../svg-transform";
|
||||
import { SvgVocabulary } from "../svg-vocabulary";
|
||||
import { SymbolContextWidget } from "../symbol-context-widget";
|
||||
import { NumericRange, range } from "../util";
|
||||
import { Random } from "../random";
|
||||
import { PointWithNormal } from "../specs";
|
||||
import { getAttachmentTransforms } from "../attach";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { createRandomColorPalette } from "../random-colors";
|
||||
import {
|
||||
CompositionContextWidget,
|
||||
createSvgCompositionContext,
|
||||
} from "../svg-composition-context";
|
||||
|
||||
type ExtendedMandalaCircleParams = MandalaCircleParams & {
|
||||
scaling: number;
|
||||
|
@ -252,10 +251,9 @@ function getRandomCircleParams(rng: Random): MandalaCircleParams {
|
|||
|
||||
export const MandalaPage: React.FC<{}> = () => {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const [bgColor, setBgColor] = useState(DEFAULT_BG_COLOR);
|
||||
const [circle1, setCircle1] = useState(CIRCLE_1_DEFAULTS);
|
||||
const [circle2, setCircle2] = useState(CIRCLE_2_DEFAULTS);
|
||||
const [baseSymbolCtx, setBaseSymbolCtx] = useState(createSvgSymbolContext());
|
||||
const [baseCompCtx, setBaseCompCtx] = useState(createSvgCompositionContext());
|
||||
const [useTwoCircles, setUseTwoCircles] = useState(false);
|
||||
const [invertCircle2, setInvertCircle2] = useState(true);
|
||||
const [firstBehindSecond, setFirstBehindSecond] = useState(false);
|
||||
|
@ -265,16 +263,10 @@ export const MandalaPage: React.FC<{}> = () => {
|
|||
setCircle2({ ...circle2, ...getRandomCircleParams(rng) });
|
||||
};
|
||||
|
||||
const symbolCtx = noFillIfShowingSpecs(baseSymbolCtx);
|
||||
const symbolCtx = noFillIfShowingSpecs(baseCompCtx);
|
||||
|
||||
const circle2SymbolCtx = invertCircle2 ? swapColors(symbolCtx) : symbolCtx;
|
||||
|
||||
const randomizeColors = () => {
|
||||
const [bgColor, stroke, fill] = createRandomColorPalette(3);
|
||||
setBgColor(bgColor);
|
||||
setBaseSymbolCtx({ ...baseSymbolCtx, stroke, fill });
|
||||
};
|
||||
|
||||
const circles = [
|
||||
<ExtendedMandalaCircle key="first" {...circle1} {...symbolCtx} />,
|
||||
];
|
||||
|
@ -291,20 +283,15 @@ export const MandalaPage: React.FC<{}> = () => {
|
|||
return (
|
||||
<>
|
||||
<h1>Mandala!</h1>
|
||||
<div className="mandala-container" style={{ backgroundColor: bgColor }}>
|
||||
<div
|
||||
className="mandala-container"
|
||||
style={{ backgroundColor: baseCompCtx.background }}
|
||||
>
|
||||
<div className="sidebar">
|
||||
<SymbolContextWidget ctx={baseSymbolCtx} onChange={setBaseSymbolCtx}>
|
||||
<ColorWidget
|
||||
label="Background"
|
||||
value={bgColor}
|
||||
onChange={setBgColor}
|
||||
/>{" "}
|
||||
</SymbolContextWidget>
|
||||
<div className="thingy">
|
||||
<button accessKey="c" onClick={randomizeColors}>
|
||||
Randomize <u>c</u>olors!
|
||||
</button>
|
||||
</div>
|
||||
<CompositionContextWidget
|
||||
ctx={baseCompCtx}
|
||||
onChange={setBaseCompCtx}
|
||||
/>
|
||||
<fieldset>
|
||||
<legend>First circle</legend>
|
||||
<ExtendedMandalaCircleParamsWidget
|
||||
|
@ -349,7 +336,11 @@ export const MandalaPage: React.FC<{}> = () => {
|
|||
</div>
|
||||
<div className="canvas">
|
||||
<HoverDebugHelper>
|
||||
<AutoSizingSvg padding={20} ref={svgRef} bgColor={bgColor}>
|
||||
<AutoSizingSvg
|
||||
padding={20}
|
||||
ref={svgRef}
|
||||
bgColor={baseCompCtx.background}
|
||||
>
|
||||
<SvgTransform transform={svgScale(0.5)}>{circles}</SvgTransform>
|
||||
</AutoSizingSvg>
|
||||
</HoverDebugHelper>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import React from "react";
|
||||
import { ColorWidget } from "./color-widget";
|
||||
import { DEFAULT_BG_COLOR } from "./colors";
|
||||
import { createRandomColorPalette } from "./random-colors";
|
||||
import { createSvgSymbolContext, SvgSymbolContext } from "./svg-symbol";
|
||||
import {
|
||||
SymbolContextWidget,
|
||||
SymbolContextWidgetProps,
|
||||
} from "./symbol-context-widget";
|
||||
|
||||
const DEFAULT_CONTEXT: SvgCompositionContext = {
|
||||
background: DEFAULT_BG_COLOR,
|
||||
...createSvgSymbolContext(),
|
||||
};
|
||||
|
||||
export type SvgCompositionContext = SvgSymbolContext & {
|
||||
/** The background color of the composition, as a hex hash (e.g. '#ff0000'). */
|
||||
background: string;
|
||||
};
|
||||
|
||||
export function createSvgCompositionContext(
|
||||
ctx: Partial<SvgCompositionContext> = {}
|
||||
): SvgCompositionContext {
|
||||
return {
|
||||
...DEFAULT_CONTEXT,
|
||||
...ctx,
|
||||
};
|
||||
}
|
||||
|
||||
export type CompositionContextWidgetProps<
|
||||
T extends SvgCompositionContext
|
||||
> = SymbolContextWidgetProps<T>;
|
||||
|
||||
export function CompositionContextWidget<T extends SvgCompositionContext>({
|
||||
ctx,
|
||||
onChange,
|
||||
children,
|
||||
}: CompositionContextWidgetProps<T>): JSX.Element {
|
||||
const randomizeColors = () => {
|
||||
const [background, stroke, fill] = createRandomColorPalette(3);
|
||||
onChange({ ...ctx, background, stroke, fill });
|
||||
};
|
||||
|
||||
const extra = (
|
||||
<button accessKey="c" onClick={randomizeColors}>
|
||||
Randomize <u>c</u>olors!
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<SymbolContextWidget ctx={ctx} onChange={onChange} extraButtons={extra}>
|
||||
{children}
|
||||
<ColorWidget
|
||||
label="Background"
|
||||
value={ctx.background}
|
||||
onChange={(background) => onChange({ ...ctx, background })}
|
||||
/>{" "}
|
||||
</SymbolContextWidget>
|
||||
);
|
||||
}
|
|
@ -30,9 +30,23 @@ export type SvgSymbolElement = (
|
|||
};
|
||||
|
||||
export type SvgSymbolContext = {
|
||||
/** The stroke color of the symbol, as a hex hash (e.g. '#ff0000'). */
|
||||
stroke: string;
|
||||
|
||||
/** The fill color of the symbol, as a hex hash (e.g. '#ff0000'). */
|
||||
fill: string;
|
||||
|
||||
/**
|
||||
* Whether or not to visibly show the specifications for the symbol,
|
||||
* e.g. its attachment points, nesting boxes, and so on.
|
||||
*/
|
||||
showSpecs: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not to forcibly apply a uniform stroke width to all
|
||||
* the shapes in the symbol. If defined, the stroke width will
|
||||
* *not* vary as the symbol is scaled.
|
||||
*/
|
||||
uniformStrokeWidth?: number;
|
||||
};
|
||||
|
||||
|
@ -43,6 +57,11 @@ const DEFAULT_CONTEXT: SvgSymbolContext = {
|
|||
uniformStrokeWidth: DEFAULT_UNIFORM_STROKE_WIDTH,
|
||||
};
|
||||
|
||||
/**
|
||||
* If the given symbol context is visibly showing its specifications,
|
||||
* return one with its fill color set to "none" so that the specs can
|
||||
* be seen more easily.
|
||||
*/
|
||||
export function noFillIfShowingSpecs<T extends SvgSymbolContext>(ctx: T): T {
|
||||
return {
|
||||
...ctx,
|
||||
|
@ -50,6 +69,9 @@ export function noFillIfShowingSpecs<T extends SvgSymbolContext>(ctx: T): T {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a symbol context with the stroke and fill colors swapped.
|
||||
*/
|
||||
export function swapColors<T extends SvgSymbolContext>(ctx: T): T {
|
||||
return {
|
||||
...ctx,
|
||||
|
|
|
@ -4,11 +4,19 @@ import { ColorWidget } from "./color-widget";
|
|||
import { NumericSlider } from "./numeric-slider";
|
||||
import { SvgSymbolContext, swapColors } from "./svg-symbol";
|
||||
|
||||
export const SymbolContextWidget: React.FC<{
|
||||
ctx: SvgSymbolContext;
|
||||
onChange: (value: SvgSymbolContext) => void;
|
||||
export type SymbolContextWidgetProps<T extends SvgSymbolContext> = {
|
||||
ctx: T;
|
||||
onChange: (value: T) => void;
|
||||
children?: any;
|
||||
}> = ({ ctx, children, onChange }) => {
|
||||
extraButtons?: JSX.Element;
|
||||
};
|
||||
|
||||
export function SymbolContextWidget<T extends SvgSymbolContext>({
|
||||
ctx,
|
||||
children,
|
||||
onChange,
|
||||
extraButtons,
|
||||
}: SymbolContextWidgetProps<T>): JSX.Element {
|
||||
const updateCtx = (updates: Partial<SvgSymbolContext>) => {
|
||||
onChange({ ...ctx, ...updates });
|
||||
};
|
||||
|
@ -29,6 +37,7 @@ export const SymbolContextWidget: React.FC<{
|
|||
<button onClick={() => updateCtx(swapColors(ctx))}>
|
||||
Swap stroke/fill
|
||||
</button>{" "}
|
||||
{extraButtons}
|
||||
<Checkbox
|
||||
label="Show specs"
|
||||
value={ctx.showSpecs}
|
||||
|
@ -48,4 +57,4 @@ export const SymbolContextWidget: React.FC<{
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue