Skip to content

Commit

Permalink
feat(ui): Change map colors according to theme in use
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Oct 23, 2021
1 parent 1920f5a commit b17745e
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 61 deletions.
32 changes: 3 additions & 29 deletions frontend/public/mapLayerRenderWebWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,6 @@
See: https://github.com/facebook/create-react-app/issues/3660#issuecomment-602098962
*/

const floorColor = hexToRgb("#0076ff");
const wallColor = hexToRgb("#333333");
const segmentColors = [
"#19A1A1",
"#7AC037",
"#DF5618",
"#F7C841",
"#9966CC" // "fallback" color
].map(function (e) {
return hexToRgb(e);
});

self.postMessage({
ready: true
});
Expand All @@ -40,13 +28,13 @@ self.addEventListener( "message", ( evt ) => {

switch (layer.type) {
case "floor":
color = floorColor;
color = evt.data.colors.floor;
break;
case "wall":
color = wallColor;
color = evt.data.colors.wall;
break;
case "segment":
color = segmentColors[colorFinder.getColor((layer.metaData.segmentId ?? ""))];
color = evt.data.colors.segments[colorFinder.getColor((layer.metaData.segmentId ?? ""))];
break;
}

Expand Down Expand Up @@ -81,20 +69,6 @@ const TYPE_SORT_MAPPING = {
"wall": 16
};

function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());

if (result === null) {
throw new Error(`Invalid color ${hex}`);
}

return {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} ;
}



class FourColorTheoremSolver {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/map/EditMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class EditMap extends Map<EditMapProps, EditMapState> {
this.drawableComponentsMutex.take(async () => {
this.drawableComponents = [];

await this.mapLayerRenderer.draw(this.props.rawMap);
await this.mapLayerRenderer.draw(this.props.rawMap, this.props.theme);
this.drawableComponents.push(this.mapLayerRenderer.getCanvas());

this.updateStructures(this.state.currentMode);
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/map/EditMapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Box, Button, CircularProgress, styled, Typography,} from "@mui/material";
import {Box, Button, CircularProgress, styled, Typography, useTheme} from "@mui/material";
import {
Capability,
useRobotMapQuery
Expand Down Expand Up @@ -36,6 +36,8 @@ const EditMapPage = (props: Record<string, never> ): JSX.Element => {
Capability.MapSegmentRename
);

const theme = useTheme();

if (mapLoadError) {
return (
<Container>
Expand Down Expand Up @@ -68,6 +70,7 @@ const EditMapPage = (props: Record<string, never> ): JSX.Element => {

return <EditMap
rawMap={mapData}
theme={theme}

supportedCapabilities={{
[Capability.CombinedVirtualRestrictions]: combinedVirtualRestrictionsCapabilitySupported,
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/map/LiveMapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Box, Button, CircularProgress, styled, Typography,} from "@mui/material";
import {Box, Button, CircularProgress, styled, Typography, useTheme} from "@mui/material";
import {Capability, useRobotMapQuery} from "../api";
import LiveMap from "./LiveMap";
import {useCapabilitiesSupported} from "../CapabilitiesProvider";
Expand Down Expand Up @@ -35,6 +35,8 @@ const LiveMapPage = (props: Record<string, never> ): JSX.Element => {
Capability.Locate
);

const theme = useTheme();

if (mapLoadError) {
return (
<Container>
Expand Down Expand Up @@ -67,6 +69,7 @@ const LiveMapPage = (props: Record<string, never> ): JSX.Element => {

return <LiveMap
rawMap={mapData}
theme={theme}

supportedCapabilities={{
[Capability.MapSegmentation]: mapSegmentationCapabilitySupported,
Expand Down
11 changes: 7 additions & 4 deletions frontend/src/map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {PathSVGDrawer} from "./PathSVGDrawer";
import {trackTransforms} from "./utils/tracked-canvas.js";
import {TouchHandler} from "./utils/touch-handling.js";
import StructureManager from "./StructureManager";
import {Box, styled} from "@mui/material";
import {Box, styled, Theme} from "@mui/material";
import SegmentLabelMapStructure from "./structures/map_structures/SegmentLabelMapStructure";
import semaphore from "semaphore";

export interface MapProps {
rawMap: RawMapData;
theme: Theme;
}

export interface MapState {
Expand Down Expand Up @@ -150,7 +151,6 @@ class Map<P, S> extends React.Component<P & MapProps, S & MapState > {
//As react-query refreshes the data when switching back to a previously invisible tab anyways,
//we can just ignore all updates while minimized/in the background to 🌈 conserve energy 🌈
if (document.visibilityState === "visible") {

if (prevProps.rawMap.metaData.nonce !== this.props.rawMap.metaData.nonce) {
this.onMapUpdate();

Expand All @@ -160,6 +160,8 @@ class Map<P, S> extends React.Component<P & MapProps, S & MapState > {
} else {
this.updateInternalDrawableState();
}
} else if (this.props.theme.palette.mode !== prevProps.theme.palette.mode) {
this.updateInternalDrawableState();
}
}
}
Expand Down Expand Up @@ -198,7 +200,7 @@ class Map<P, S> extends React.Component<P & MapProps, S & MapState > {
this.drawableComponentsMutex.take(async () => {
this.drawableComponents = [];

await this.mapLayerRenderer.draw(this.props.rawMap);
await this.mapLayerRenderer.draw(this.props.rawMap, this.props.theme);
this.drawableComponents.push(this.mapLayerRenderer.getCanvas());

for (const entity of this.props.rawMap.entities) {
Expand All @@ -209,7 +211,8 @@ class Map<P, S> extends React.Component<P & MapProps, S & MapState > {
entity,
this.props.rawMap.size.x,
this.props.rawMap.size.y,
this.props.rawMap.pixelSize
this.props.rawMap.pixelSize,
this.props.theme
);

this.drawableComponents.push(pathImg);
Expand Down
98 changes: 78 additions & 20 deletions frontend/src/map/MapLayerRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
import {RawMapData} from "../api";
import {FourColorTheoremSolver} from "./utils/map-color-finder";
import {Theme} from "@mui/material";

type RGBColor = {
r: number;
g: number;
b: number;
}

//adapted from https://stackoverflow.com/a/60880664
function adjustBrightness(hexInput: string, percent: number) : string {
let hex = hexInput;

// strip the leading # if it's there
hex = hex.replace(/^\s*#|\s*$/g, "");

// convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
if (hex.length === 3) {
hex = hex.replace(/(.)/g, "$1$1");
}

let r = parseInt(hex.substr(0, 2), 16);
let g = parseInt(hex.substr(2, 2), 16);
let b = parseInt(hex.substr(4, 2), 16);

const calculatedPercent = (100 + percent) / 100;

r = Math.round(Math.min(255, Math.max(0, r * calculatedPercent)));
g = Math.round(Math.min(255, Math.max(0, g * calculatedPercent)));
b = Math.round(Math.min(255, Math.max(0, b * calculatedPercent)));

let result = "#";

result += r.toString(16).toUpperCase().padStart(2, "0");
result += g.toString(16).toUpperCase().padStart(2, "0");
result += b.toString(16).toUpperCase().padStart(2, "0");

return result;
}

function hexToRgb(hex: string) : RGBColor {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());

Expand All @@ -27,13 +59,12 @@ export class MapLayerRenderer {
private width: number;
private height: number;

private readonly floorColor: RGBColor;
private readonly wallColor: RGBColor;
private readonly segmentColors: Array<RGBColor>;

private mapLayerRenderWebWorker: Worker;
private mapLayerRenderWebWorkerAvailable = false;
private pendingCallback: any;
private colors: { floor: string; wall: string; segments: string[] };
private darkColors: { floor: RGBColor; wall: RGBColor; segments: RGBColor[] };
private lightColors: { floor: RGBColor; wall: RGBColor; segments: RGBColor[] };

constructor() {
this.width = 1;
Expand All @@ -49,18 +80,33 @@ export class MapLayerRenderer {
throw new Error("Context is null");
}

this.floorColor = hexToRgb("#0076ff");
this.wallColor = hexToRgb("#333333");
this.segmentColors = [
"#19A1A1",
"#7AC037",
"#DF5618",
"#F7C841",
"#9966CC" // "fallback" color
].map(function (e) {
return hexToRgb(e);
});
this.colors = {
floor:"#0076ff",
wall: "#333333",
segments: [
"#19A1A1",
"#7AC037",
"#DF5618",
"#F7C841",
"#9966CC" // "fallback" color
]
};

this.darkColors = {
floor: hexToRgb(adjustBrightness(this.colors.floor, -20)),
wall: hexToRgb(this.colors.wall),
segments: this.colors.segments.map((e) => {
return hexToRgb(adjustBrightness(e, -20));
})
};

this.lightColors = {
floor: hexToRgb(this.colors.floor),
wall: hexToRgb(this.colors.wall),
segments: this.colors.segments.map((e) => {
return hexToRgb(e);
})
};

this.mapLayerRenderWebWorker = new Worker("mapLayerRenderWebWorker.js");

Expand Down Expand Up @@ -99,7 +145,18 @@ export class MapLayerRenderer {
this.pendingCallback = undefined;
}

draw(data : RawMapData): Promise<void> {
draw(data : RawMapData, theme: Theme): Promise<void> {
let colorsToUse: { floor: RGBColor; wall: RGBColor; segments: RGBColor[]; };

switch (theme.palette.mode) {
case "light":
colorsToUse = this.lightColors;
break;
case "dark":
colorsToUse = this.darkColors;
break;
}

return new Promise((resolve, reject) => {
if (this.ctx === null) {
throw new Error("Context is null");
Expand All @@ -124,7 +181,8 @@ export class MapLayerRenderer {
width: this.width,
height: this.height,
mapLayers: data.layers,
pixelSize: data.pixelSize
pixelSize: data.pixelSize,
colors: colorsToUse
});

//I'm not 100% sure if this cleanup is necessary but it should prevent eternally stuck promises
Expand All @@ -148,13 +206,13 @@ export class MapLayerRenderer {

switch (layer.type) {
case "floor":
color = this.floorColor;
color = colorsToUse.floor;
break;
case "wall":
color = this.wallColor;
color = colorsToUse.wall;
break;
case "segment":
color = this.segmentColors[colorFinder.getColor((layer.metaData.segmentId ?? ""))];
color = colorsToUse.segments[colorFinder.getColor((layer.metaData.segmentId ?? ""))];
break;
}

Expand Down
21 changes: 16 additions & 5 deletions frontend/src/map/PathSVGDrawer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {RawMapEntity, RawMapEntityType} from "../api";
import {Theme} from "@mui/material";

export class PathSVGDrawer {
static drawPathSVG(path: RawMapEntity, mapWidth: number, mapHeight: number, pixelSize: number) : Promise<HTMLImageElement> {
static drawPathSVG(path: RawMapEntity, mapWidth: number, mapHeight: number, pixelSize: number, theme: Theme) : Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
if (!(path.type === RawMapEntityType.Path || path.type === RawMapEntityType.PredictedPath)) {
return reject("Not a path");
}
const img = new Image();

if (path.points.length > 0) {
img.src = PathSVGDrawer.createDataUrlFromPoints(path.points, path.type, mapWidth, mapHeight, pixelSize);
img.src = PathSVGDrawer.createDataUrlFromPoints(path.points, path.type, mapWidth, mapHeight, pixelSize, theme);
img.decode().then(() => {
resolve(img);
}).catch(err => {
Expand All @@ -22,8 +23,18 @@ export class PathSVGDrawer {
}


private static createDataUrlFromPoints(points: Array<number>, type: RawMapEntityType, mapWidth: number, mapHeight: number, pixelSize: number) {
private static createDataUrlFromPoints(points: Array<number>, type: RawMapEntityType, mapWidth: number, mapHeight: number, pixelSize: number, theme : Theme) {
let svgPath = `<svg xmlns="http://www.w3.org/2000/svg" width="${mapWidth}" height="${mapHeight}" viewBox="0 0 ${mapWidth} ${mapHeight}"><path d="`;
let pathColor;

switch (theme.palette.mode) {
case "light":
pathColor = "#ffffff";
break;
case "dark":
pathColor = "#000000";
break;
}

for (let i = 0; i < points.length; i = i + 2) {
let type = "L";
Expand All @@ -35,15 +46,15 @@ export class PathSVGDrawer {
svgPath += `${type} ${points[i] / pixelSize} ${points[i + 1] / pixelSize} `;
}

svgPath += "\" fill=\"none\" stroke=\"" + "#000000" + "\" stroke-width=\"0.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
svgPath += `" fill="none" stroke="${pathColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"`;

if (type === RawMapEntityType.PredictedPath) {
svgPath += " stroke-dasharray=\"1,1\"";
}

svgPath += "/></svg>";

return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgPath);
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgPath)}`;

}
}

0 comments on commit b17745e

Please sign in to comment.