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
Atul Varma 2021-04-02 16:33:07 -04:00 zatwierdzone przez GitHub
rodzic 7c05f78fd9
commit e48b5f9bae
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
7 zmienionych plików z 132 dodań i 60 usunięć

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -8,7 +8,7 @@ type CheckboxProps = {
export const Checkbox: React.FC<CheckboxProps> = (props) => {
return (
<label>
<label className="checkbox">
<input
type="checkbox"
checked={props.value}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>
);
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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>
);
};
}