add pixel picker (fixes #55)

right click & middle click now get triggered by keybinds (uses auxclick event)
This commit is contained in:
Grant 2024-07-02 13:20:14 -06:00
parent 7964954a0d
commit 0e97316096
5 changed files with 87 additions and 44 deletions

View File

@ -33,10 +33,6 @@ const Cursor = () => {
const { cursor } = useAppContext();
const [color, setColor] = useState<string>();
useEffect(() => {
console.log("color", color);
}, [color]);
useEffect(() => {
if (typeof cursor.color === "number") {
const color = Canvas.instance?.Pallete.getColor(cursor.color);
@ -71,17 +67,19 @@ const CanvasInner = () => {
useAppContext();
const PanZoom = useContext(RendererContext);
const handlePixelWhois = useCallback(
({ clientX, clientY }: { clientX: number; clientY: number }) => {
/**
* Is the canvas coordinate within the bounds of the canvas?
*/
const isCoordInCanvas = useCallback(
(x: number, y: number): boolean => {
if (!canvas.current) {
console.warn(
"[CanvasWrapper#handlePixelWhois] canvas instance does not exist"
"[CanvasWrapper#isCoordInCanvas] canvas instance does not exist"
);
return;
return false;
}
const [x, y] = canvas.current.screenToPos(clientX, clientY);
if (x < 0 || y < 0) return; // discard if out of bounds
if (x < 0 || y < 0) return false; // not positive, impossible to be on canvas
// canvas size can dynamically change, so we need to check the current config
// we're depending on canvas.instance's config so we don't have to use a react dependency
@ -92,14 +90,31 @@ const CanvasInner = () => {
},
} = canvas.current.getConfig();
if (x >= width || y >= height) return; // out of bounds
if (x >= width || y >= height) return false; // out of bounds
} else {
// although this should never happen, log it
console.warn(
"[CanvasWrapper#handlePixelWhois] canvas config is not available yet"
"[CanvasWrapper#isCoordInCanvas] canvas config is not available yet"
);
}
return true;
},
[canvas.current]
);
const handlePixelWhois = useCallback(
({ clientX, clientY }: { clientX: number; clientY: number }) => {
if (!canvas.current) {
console.warn(
"[CanvasWrapper#handlePixelWhois] canvas instance does not exist"
);
return;
}
const [x, y] = canvas.current.screenToPos(clientX, clientY);
if (!isCoordInCanvas(x, y)) return; // out of bounds
// .......
// .......
// .......
@ -114,6 +129,30 @@ const CanvasInner = () => {
[canvas.current]
);
const handlePickPixel = useCallback(
({ clientX, clientY }: { clientX: number; clientY: number }) => {
if (!canvas.current) {
console.warn(
"[CanvasWrapper#handlePickPixel] canvas instance does not exist"
);
return;
}
const [x, y] = canvas.current.screenToPos(clientX, clientY);
if (!isCoordInCanvas(x, y)) return; // out of bounds
const pixel = canvas.current.getPixel(x, y);
if (!pixel) return;
// no need to use canvas#setCursor as Palette.tsx already does that
setCursor((v) => ({
...v,
color: pixel.color,
}));
},
[canvas.current]
);
useEffect(() => {
if (!canvasRef.current) return;
canvas.current = new Canvas(canvasRef.current!, PanZoom);
@ -126,9 +165,11 @@ const CanvasInner = () => {
});
KeybindManager.on("PIXEL_WHOIS", handlePixelWhois);
KeybindManager.on("PICK_COLOR", handlePickPixel);
return () => {
KeybindManager.off("PIXEL_WHOIS", handlePixelWhois);
KeybindManager.off("PICK_COLOR", handlePickPixel);
canvas.current!.destroy();
};
}, [PanZoom]);

View File

@ -3,21 +3,14 @@ import { useAppContext } from "../../contexts/AppContext";
import { Canvas } from "../../lib/canvas";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { IPaletteContext } from "@sc07-canvas/lib/src/net";
import { KeybindManager } from "../../lib/keybinds";
export const Palette = () => {
const { config, user, setCursor } = useAppContext<true>();
const [pallete, setPallete] = useState<IPaletteContext>({});
const { config, user, cursor, setCursor } = useAppContext<true>();
useEffect(() => {
Canvas.instance?.updatePallete(pallete);
setCursor((v) => ({
...v,
color: pallete.color,
}));
}, [pallete]);
Canvas.instance?.updateCursor(cursor.color);
}, [cursor]);
useEffect(() => {
const handleDeselect = () => {
@ -42,8 +35,8 @@ export const Palette = () => {
className="pallete-color--deselect"
title="Deselect Color"
onClick={() => {
setPallete(({ color, ...pallete }) => {
return pallete;
setCursor(({ color, ...cursor }) => {
return cursor;
});
}}
>
@ -53,7 +46,7 @@ export const Palette = () => {
<button
key={color.id}
aria-label={color.name}
className={["pallete-color", color.id === pallete.color && "active"]
className={["pallete-color", color.id === cursor.color && "active"]
.filter((a) => a)
.join(" ")}
style={{
@ -61,9 +54,9 @@ export const Palette = () => {
}}
title={color.name}
onClick={() => {
setPallete((pallete) => {
setCursor((cursor) => {
return {
...pallete,
...cursor,
color: color.id,
};
});

View File

@ -1,10 +1,5 @@
import EventEmitter from "eventemitter3";
import {
ClientConfig,
IPaletteContext,
IPosition,
Pixel,
} from "@sc07-canvas/lib/src/net";
import { ClientConfig, IPosition, Pixel } from "@sc07-canvas/lib/src/net";
import Network from "./network";
import {
ClickEvent,
@ -34,7 +29,7 @@ export class Canvas extends EventEmitter<CanvasEvents> {
private canvas: HTMLCanvasElement;
private PanZoom: PanZoom;
private cursor = { x: -1, y: -1 };
private cursor: { x: number; y: number; color?: number } = { x: -1, y: -1 };
private pixels: {
[x_y: string]: { color: number; type: "full" | "pending" };
} = {};
@ -174,6 +169,10 @@ export class Canvas extends EventEmitter<CanvasEvents> {
return pixels;
}
getPixel(x: number, y: number): { color: number } | undefined {
return this.pixels[x + "_" + y];
}
handleLongPress = (clientX: number, clientY: number) => {
KeybindManager.handleInteraction(
{
@ -289,16 +288,15 @@ export class Canvas extends EventEmitter<CanvasEvents> {
getRenderer().usePixel({ x, y, hex: palette?.hex || "null" });
};
palleteCtx: IPaletteContext = {};
Pallete = {
getColor: (colorId: number) => {
return this.config.pallete.colors.find((c) => c.id === colorId);
},
getSelectedColor: () => {
if (!this.palleteCtx.color) return undefined;
if (!this.cursor.color) return undefined;
return this.Pallete.getColor(this.palleteCtx.color);
return this.Pallete.getColor(this.cursor.color);
},
getColorFromHex: (hex: string) => {
@ -306,8 +304,14 @@ export class Canvas extends EventEmitter<CanvasEvents> {
},
};
updatePallete(pallete: IPaletteContext) {
this.palleteCtx = pallete;
/**
* Changes the cursor color as tracked by the Canvas instance
*
* @see Toolbar/Palette.tsx
* @param color
*/
updateCursor(color?: number) {
this.cursor.color = color;
}
place(x: number, y: number) {

View File

@ -59,6 +59,11 @@ const KEYBINDS = enforceObjectType({
key: "Escape",
},
],
PICK_COLOR: [
{
key: "MCLICK",
},
],
});
class KeybindManager_ extends EventEmitter<{
@ -72,7 +77,11 @@ class KeybindManager_ extends EventEmitter<{
passive: false,
});
document.addEventListener("keyup", this.handleKeyup);
document.addEventListener("click", this.handleClick);
document.addEventListener("click", this.handleClick); // only gets triggered for left click
document.addEventListener("auxclick", (e) => {
if (e.button === 0) return; // left button still triggers this
this.handleClick(e);
});
}
destroy() {

View File

@ -57,10 +57,6 @@ export interface IPosition {
y: number;
}
export interface IPaletteContext {
color?: number;
}
// other
export type Pixel = {