extract rgba resolving to provider

This commit is contained in:
Vinzenz Schroeter 2024-05-07 11:26:07 +02:00
parent 102c084328
commit e8238c6ea7
4 changed files with 107 additions and 49 deletions

View file

@ -1,3 +1,5 @@
import {useState} from 'react';
import ClientScreen from './ClientScreen'; import ClientScreen from './ClientScreen';
import Controls from './Controls.tsx'; import Controls from './Controls.tsx';
import JoinForm from './JoinForm.tsx'; import JoinForm from './JoinForm.tsx';
@ -7,38 +9,39 @@ import Row from './components/Row.tsx';
import Scoreboard from './Scoreboard.tsx'; import Scoreboard from './Scoreboard.tsx';
import Button from './components/Button.tsx'; import Button from './components/Button.tsx';
import MapChooser from './MapChooser.tsx'; import MapChooser from './MapChooser.tsx';
import {getRandomTheme, RgbaThemeProvider, ThemeContext, useStoredTheme} from './theme.tsx';
import './App.css'; import './App.css';
import {ThemeContext, getRandomTheme, useStoredTheme} from './theme.ts';
import {useState} from 'react';
export default function App() { export default function App() {
const [theme, setTheme] = useStoredTheme(); const [theme, setTheme] = useStoredTheme();
const [name, setName] = useState<string | null>(null); const [name, setName] = useState<string | null>(null);
return <ThemeContext.Provider value={theme}> return <ThemeContext.Provider value={theme}>
<Column className="flex-grow"> <RgbaThemeProvider>
<Column className="flex-grow">
<ClientScreen player={name}/> <ClientScreen player={name}/>
<Row> <Row>
<h1 className="flex-grow">CCCB-Tanks!</h1> <h1 className="flex-grow">CCCB-Tanks!</h1>
<MapChooser /> <MapChooser/>
<Button text="☼ change colors" onClick={() => setTheme(_ => getRandomTheme())}/> <Button text="☼ change colors" onClick={() => setTheme(_ => getRandomTheme())}/>
<Button <Button
onClick={() => window.open('https://github.com/kaesaecracker/cccb-tanks-cs', '_blank')?.focus()} onClick={() => window.open('https://github.com/kaesaecracker/cccb-tanks-cs', '_blank')?.focus()}
text="⌂ source"/> text="⌂ source"/>
{name !== '' && {name !== '' &&
<Button onClick={() => setName(_ => '')} text="∩ logout"/>} <Button onClick={() => setName(_ => '')} text="∩ logout"/>}
</Row> </Row>
{name || <JoinForm onDone={name => setName(_ => name)}/>} {name || <JoinForm onDone={name => setName(_ => name)}/>}
<Row className="GadgetRows"> <Row className="GadgetRows">
{name && <Controls player={name}/>} {name && <Controls player={name}/>}
{name && <PlayerInfo player={name}/>} {name && <PlayerInfo player={name}/>}
<Scoreboard/> <Scoreboard/>
</Row> </Row>
</Column> </Column>
</RgbaThemeProvider>
</ThemeContext.Provider>; </ThemeContext.Provider>;
} }

View file

@ -1,6 +1,6 @@
import {hslToString, ThemeContext} from '../theme.ts'; import {useEffect, useRef} from 'react';
import {useContext, useEffect, useRef} from 'react';
import './PixelGridCanvas.css'; import './PixelGridCanvas.css';
import {useRgbaThemeContext} from '../theme.tsx';
const pixelsPerRow = 352; const pixelsPerRow = 352;
const pixelsPerCol = 160; const pixelsPerCol = 160;
@ -20,12 +20,6 @@ function getPixelDataIndexes(bitIndex: number) {
}; };
} }
function normalizeColor(context: CanvasRenderingContext2D, color: string) {
context.fillStyle = color;
context.fillRect(0, 0, 1, 1);
return context.getImageData(0, 0, 1, 1).data;
}
function parseAdditionalDataNibble(nibble: number) { function parseAdditionalDataNibble(nibble: number) {
const isPlayerMask = 1; const isPlayerMask = 1;
const entityTypeMask = 12; const entityTypeMask = 12;
@ -107,7 +101,7 @@ function drawPixelsToCanvas(
export default function PixelGridCanvas({pixels}: { export default function PixelGridCanvas({pixels}: {
readonly pixels: Uint8ClampedArray; readonly pixels: Uint8ClampedArray;
}) { }) {
const theme = useContext(ThemeContext); const theme = useRgbaThemeContext();
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => { useEffect(() => {
@ -137,12 +131,12 @@ export default function PixelGridCanvas({pixels}: {
pixels, pixels,
additional: additionalData, additional: additionalData,
colors: { colors: {
background: normalizeColor(drawContext, hslToString(theme.background)), background: theme.background,
foreground: normalizeColor(drawContext, hslToString(theme.primary)), foreground: theme.primary,
player: normalizeColor(drawContext, hslToString(theme.secondary)), player: theme.secondary,
tanks: normalizeColor(drawContext, hslToString(theme.tertiary)), tanks: theme.tertiary,
powerUps: normalizeColor(drawContext, hslToString(theme.tertiary)), powerUps: theme.tertiary,
bullets: normalizeColor(drawContext, hslToString(theme.tertiary)) bullets: theme.tertiary
} }
}); });
}; };

View file

@ -1,7 +1,7 @@
import {ChangeEventHandler} from "react"; import {ChangeEventHandler} from "react";
import './TextInput.css'; import './TextInput.css';
export default function TextInput(props: { export default function TextInput( {onChange, className, value, placeholder, onEnter }: {
onChange?: ChangeEventHandler<HTMLInputElement> | undefined; onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
className?: string; className?: string;
value: string; value: string;
@ -9,13 +9,14 @@ export default function TextInput(props: {
onEnter?: () => void; onEnter?: () => void;
}) { }) {
return <input return <input
{...props}
type="text" type="text"
className={'TextInput ' + (props.className?? '')} className={'TextInput ' + (className?? '')}
value={value}
placeholder={placeholder}
onChange={onChange}
onKeyUp={event => { onKeyUp={event => {
if (props.onEnter && event.key === 'Enter') if (onEnter && event.key === 'Enter')
props.onEnter(); onEnter();
}} }}
/>; />;
} }

View file

@ -1,5 +1,11 @@
import {useStoredObjectState} from './useStoredState.ts'; import {useStoredObjectState} from './useStoredState.ts';
import {createContext} from 'react'; import {createContext, ReactNode, useContext, useEffect, useRef, useState} from 'react';
export type HSL = {
h: number;
s: number;
l: number;
}
export type Theme = { export type Theme = {
primary: HSL; primary: HSL;
@ -15,12 +21,6 @@ function getRandom(min: number, max: number) {
return min + Math.random() * (max - min); return min + Math.random() * (max - min);
} }
type HSL = {
h: number;
s: number;
l: number;
}
function getRandomHsl(params: { function getRandomHsl(params: {
minHue?: number, minHue?: number,
maxHue?: number, maxHue?: number,
@ -91,3 +91,63 @@ export function useStoredTheme() {
} }
export const ThemeContext = createContext<Theme>(getRandomTheme()); export const ThemeContext = createContext<Theme>(getRandomTheme());
type Rgba = Uint8ClampedArray;
export type RgbaTheme = {
primary: Rgba;
secondary: Rgba;
background: Rgba;
tertiary: Rgba;
}
const dummyRgbaTheme: RgbaTheme = {
primary: new Uint8ClampedArray([0, 0, 0, 0]),
secondary: new Uint8ClampedArray([0, 0, 0, 0]),
background: new Uint8ClampedArray([0, 0, 0, 0]),
tertiary: new Uint8ClampedArray([0, 0, 0, 0])
};
const RgbaThemeContext = createContext<RgbaTheme>(dummyRgbaTheme);
function normalizeColor(context: CanvasRenderingContext2D, color: HSL) {
context.fillStyle = hslToString(color);
context.fillRect(0, 0, 1, 1);
return context.getImageData(0, 0, 1, 1).data;
}
export function useRgbaThemeContext() {
return useContext(RgbaThemeContext);
}
export function RgbaThemeProvider({children}: { children: ReactNode }) {
const hslTheme = useContext(ThemeContext);
const [rgbaTheme, setRgbaTheme] = useState<RgbaTheme | null>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas === null)
throw new Error('canvas null');
const drawContext = canvas.getContext('2d', {
alpha: true,
colorSpace: 'srgb',
willReadFrequently: true
});
if (!drawContext)
throw new Error('could not get draw context');
setRgbaTheme({
background: normalizeColor(drawContext, hslTheme.background),
primary: normalizeColor(drawContext, hslTheme.primary),
secondary: normalizeColor(drawContext, hslTheme.secondary),
tertiary: normalizeColor(drawContext, hslTheme.tertiary),
});
}, [hslTheme, canvasRef.current]);
return <RgbaThemeContext.Provider value={rgbaTheme || dummyRgbaTheme}>
<canvas hidden={true} ref={canvasRef}/>
{children}
</RgbaThemeContext.Provider>;
}