mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-25 07:28:55 -05:00
feat: implements adding node to scene
This commit is contained in:
@@ -8,7 +8,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const COLOR = "grey.900";
|
||||
const SIZE = 14;
|
||||
const SIZE = 11;
|
||||
const ANIMATIONS = {
|
||||
in: keyframes`
|
||||
0% {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useGlobalState } from "../../hooks/useGlobalState";
|
||||
import { ContextMenu } from "./ContextMenu";
|
||||
import { ContextMenuItem } from "./ContextMenuItem";
|
||||
|
||||
31
src/components/ContextMenus/TileContextMenu.tsx
Normal file
31
src/components/ContextMenus/TileContextMenu.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { useGlobalState } from "../../hooks/useGlobalState";
|
||||
import { ContextMenu } from "./ContextMenu";
|
||||
import { ContextMenuItem } from "./ContextMenuItem";
|
||||
import { Coords } from "../../renderer/elements/Coords";
|
||||
import { Add } from "@mui/icons-material";
|
||||
|
||||
interface Props {
|
||||
tile: Coords;
|
||||
}
|
||||
|
||||
export const TileContextMenu = ({ tile }: Props) => {
|
||||
const renderer = useGlobalState((state) => state.renderer);
|
||||
const icons = useGlobalState((state) => state.initialScene.icons);
|
||||
const position = renderer.getTileScreenPosition(tile.x, tile.y);
|
||||
|
||||
return (
|
||||
<ContextMenu position={position}>
|
||||
<ContextMenuItem
|
||||
onClick={() =>
|
||||
renderer.sceneElements.nodes.addNode({
|
||||
position: tile,
|
||||
iconId: icons[0].id,
|
||||
})
|
||||
}
|
||||
icon={<Add />}
|
||||
label="Add node"
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,24 @@
|
||||
import React from "react";
|
||||
import { useGlobalState } from "../../hooks/useGlobalState";
|
||||
import { NodeContextMenu } from "./NodeContextMenu";
|
||||
import { TileContextMenu } from "./TileContextMenu";
|
||||
import { Node } from "../../renderer/elements/Node";
|
||||
import { Coords } from "../../renderer/elements/Coords";
|
||||
|
||||
export const ContextMenu = () => {
|
||||
const targetElement = useGlobalState((state) => state.showContextMenuAt);
|
||||
const targetElement = useGlobalState((state) => state.showContextMenuFor);
|
||||
|
||||
if (!targetElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <NodeContextMenu node={targetElement} />;
|
||||
if (targetElement instanceof Node) {
|
||||
return <NodeContextMenu node={targetElement} />;
|
||||
}
|
||||
|
||||
if (targetElement instanceof Coords) {
|
||||
return <TileContextMenu tile={targetElement} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { create } from "zustand";
|
||||
import { SceneI } from "../validation/SceneSchema";
|
||||
import { Node } from "../renderer/elements/Node";
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
import { Renderer } from "../renderer/Renderer";
|
||||
import { OnSceneChange, SceneEventI } from "../types";
|
||||
|
||||
interface GlobalState {
|
||||
showContextMenuAt: Node | null;
|
||||
showContextMenuFor: Node | Coords | null;
|
||||
onSceneChange: OnSceneChange;
|
||||
setOnSceneChange: (onSceneChange: OnSceneChange) => void;
|
||||
initialScene: SceneI;
|
||||
@@ -20,7 +21,7 @@ interface GlobalState {
|
||||
}
|
||||
|
||||
export const useGlobalState = create<GlobalState>((set, get) => ({
|
||||
showContextMenuAt: null,
|
||||
showContextMenuFor: null,
|
||||
selectedElements: [],
|
||||
selectedSideNavItem: null,
|
||||
onSceneChange: () => {},
|
||||
@@ -45,8 +46,8 @@ export const useGlobalState = create<GlobalState>((set, get) => ({
|
||||
const { renderer } = get();
|
||||
|
||||
switch (event.type) {
|
||||
case "GRID_SELECTED":
|
||||
set({ showContextMenuAt: null, selectedElements: [] });
|
||||
case "TILE_SELECTED":
|
||||
set({ showContextMenuFor: event.data.tile, selectedElements: [] });
|
||||
break;
|
||||
case "NODES_SELECTED":
|
||||
set({ selectedElements: event.data.nodes });
|
||||
@@ -55,21 +56,21 @@ export const useGlobalState = create<GlobalState>((set, get) => ({
|
||||
const node = renderer.sceneElements.nodes.getNodeById(
|
||||
event.data.nodes[0]
|
||||
);
|
||||
set({ showContextMenuAt: node });
|
||||
set({ showContextMenuFor: node });
|
||||
}
|
||||
break;
|
||||
case "NODE_REMOVED":
|
||||
set({
|
||||
showContextMenuAt: null,
|
||||
showContextMenuFor: null,
|
||||
selectedElements: [],
|
||||
selectedSideNavItem: null,
|
||||
});
|
||||
break;
|
||||
case "NODE_MOVED":
|
||||
set({ showContextMenuAt: null, selectedElements: [] });
|
||||
set({ showContextMenuFor: null, selectedElements: [] });
|
||||
break;
|
||||
case "ZOOM_CHANGED":
|
||||
set({ showContextMenuAt: null, selectedElements: [] });
|
||||
set({ showContextMenuFor: null, selectedElements: [] });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
|
||||
interface MouseCoords {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
|
||||
interface MousePosition {
|
||||
position: MouseCoords;
|
||||
delta: MouseCoords | null;
|
||||
position: Coords;
|
||||
delta: Coords | null;
|
||||
}
|
||||
|
||||
type _MouseEvent = (e: MousePosition) => void;
|
||||
@@ -52,26 +48,23 @@ export const useMouseInput = () => {
|
||||
(e: MouseEvent) => {
|
||||
const offset = getOffset(domEl);
|
||||
|
||||
return {
|
||||
x: e.clientX - offset.left + window.scrollX,
|
||||
y: e.clientY - offset.top + window.scrollY,
|
||||
};
|
||||
return new Coords(
|
||||
e.clientX - offset.left + window.scrollX,
|
||||
e.clientY - offset.top + window.scrollY
|
||||
);
|
||||
},
|
||||
[domEl]
|
||||
);
|
||||
|
||||
const parseMousePosition = useCallback(
|
||||
(e: MouseEvent, mousedown: MouseCoords | null) => {
|
||||
(e: MouseEvent, mousedown: Coords | null) => {
|
||||
const current = mouseEventToCoords(e);
|
||||
const delta = mousedown
|
||||
? {
|
||||
x: current.x - mousedown.x,
|
||||
y: current.y - mousedown.y,
|
||||
}
|
||||
? new Coords(current.x - mousedown.x, current.y - mousedown.y)
|
||||
: null;
|
||||
|
||||
return {
|
||||
position: { x: current.x, y: current.y },
|
||||
position: new Coords(current.x, current.y),
|
||||
delta,
|
||||
};
|
||||
},
|
||||
@@ -81,7 +74,7 @@ export const useMouseInput = () => {
|
||||
useEffect(() => {
|
||||
if (!callbacks || !domEl) return;
|
||||
|
||||
let lastPosition: MouseCoords | null = null;
|
||||
let lastPosition: Coords | null = null;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
callbacks.onMouseDown(parseMousePosition(e, lastPosition));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { Renderer } from "../renderer/Renderer";
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
import { ModeBase } from "./ModeBase";
|
||||
import type { Mouse, OnSceneChange } from "../types";
|
||||
|
||||
@@ -12,7 +13,7 @@ export class ModeManager {
|
||||
} = undefined;
|
||||
lastMode?: typeof ModeBase = undefined;
|
||||
mouse: Mouse = {
|
||||
position: { x: 0, y: 0 },
|
||||
position: new Coords(0, 0),
|
||||
delta: null,
|
||||
};
|
||||
emitEvent?: OnSceneChange;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Mouse } from "../types";
|
||||
import { getTargetFromSelection, isMouseOverNewTile } from "./utils";
|
||||
import { SelectNode } from "./SelectNode";
|
||||
import { Node } from "../renderer/elements/Node";
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
|
||||
export class Select extends ModeBase {
|
||||
entry(mouse: Mouse) {
|
||||
@@ -43,7 +44,10 @@ export class Select extends ModeBase {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctx.emitEvent({ type: "GRID_SELECTED" });
|
||||
this.ctx.emitEvent({
|
||||
type: "TILE_SELECTED",
|
||||
data: { tile: new Coords(x, y) },
|
||||
});
|
||||
}
|
||||
|
||||
MOUSE_MOVE(mouse: Mouse) {
|
||||
|
||||
14
src/renderer/elements/Coords.ts
Normal file
14
src/renderer/elements/Coords.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export class Coords {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
set(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
getBoundingBox,
|
||||
getTileBounds,
|
||||
} from "../utils/gridHelpers";
|
||||
import type { Context, Coords } from "../../types";
|
||||
import type { Context } from "../../types";
|
||||
import { Coords } from "./Coords";
|
||||
import { SceneElement } from "../SceneElement";
|
||||
import { tweenPosition } from "../../utils";
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { Group } from "paper";
|
||||
import { Coords, Context } from "../../types";
|
||||
import { Context } from "../../types";
|
||||
import { Coords } from "./Coords";
|
||||
import { theme } from "../../theme";
|
||||
import { NodeTile } from "./NodeTile";
|
||||
import { NodeIcon } from "./NodeIcon";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Group, Raster } from "paper";
|
||||
import { PROJECTED_TILE_WIDTH, PIXEL_UNIT } from "../constants";
|
||||
import { Coords, Context } from "../../types";
|
||||
import { Context } from "../../types";
|
||||
|
||||
const NODE_IMG_PADDING = 0 * PIXEL_UNIT;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import autobind from "auto-bind";
|
||||
import { makeAutoObservable, toJS } from "mobx";
|
||||
import { Context } from "../../types";
|
||||
import { Node, NodeOptions } from "./Node";
|
||||
import { Coords } from "./Coords";
|
||||
import cuid from "cuid";
|
||||
import { tweenPosition } from "../../utils";
|
||||
|
||||
@@ -19,11 +20,19 @@ export class Nodes {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
addNode(options: NodeOptions) {
|
||||
addNode(
|
||||
options: Omit<NodeOptions, "position" | "id"> & {
|
||||
id?: string;
|
||||
position: { x: number; y: number };
|
||||
}
|
||||
) {
|
||||
const position = new Coords(options.position.x, options.position.y);
|
||||
|
||||
const node = new Node(
|
||||
this.ctx,
|
||||
{
|
||||
...options,
|
||||
position,
|
||||
id: options.id ?? cuid(),
|
||||
},
|
||||
{
|
||||
@@ -42,14 +51,11 @@ export class Nodes {
|
||||
}
|
||||
|
||||
onMove(x: number, y: number, node: Node, opts?: { skipAnimation: boolean }) {
|
||||
const from = node.position;
|
||||
const to = { x, y };
|
||||
const from = new Coords(node.position.x, node.position.y);
|
||||
const to = new Coords(x, y);
|
||||
|
||||
const tile = this.ctx.getTileBounds(x, y);
|
||||
node.position = {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
node.position = new Coords(x, y);
|
||||
|
||||
tweenPosition(node.container, {
|
||||
...tile.bottom,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PROJECTED_TILE_HEIGHT, PROJECTED_TILE_WIDTH } from "../constants";
|
||||
import { Coords } from "../../types";
|
||||
import { Coords } from "../elements/Coords";
|
||||
|
||||
// Iterates over every item in a 2 dimensional array
|
||||
// const tileIterator = (w, h, cb) => {
|
||||
@@ -45,15 +45,15 @@ export const sortByPosition = (items: Coords[]) => {
|
||||
|
||||
export const getBoundingBox = (
|
||||
tiles: Coords[],
|
||||
offset: Coords = { x: 0, y: 0 }
|
||||
offset: Coords = new Coords(0, 0)
|
||||
) => {
|
||||
const { lowX, lowY, highX, highY } = sortByPosition(tiles);
|
||||
|
||||
return [
|
||||
{ x: lowX - offset.x, y: lowY - offset.y },
|
||||
{ x: highX + offset.x, y: lowY - offset.y },
|
||||
{ x: highX + offset.x, y: highY + offset.y },
|
||||
{ x: lowX - offset.x, y: highY + offset.y },
|
||||
new Coords(lowX - offset.x, lowY - offset.y),
|
||||
new Coords(highX + offset.x, lowY - offset.y),
|
||||
new Coords(highX + offset.x, highY + offset.y),
|
||||
new Coords(lowX - offset.x, highY + offset.y),
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
21
src/types.ts
21
src/types.ts
@@ -1,6 +1,6 @@
|
||||
import { Renderer } from "./renderer/Renderer";
|
||||
import type { ModeManager } from "./modes/ModeManager";
|
||||
import type { Node } from "./renderer/elements/Node";
|
||||
import { Coords } from "./renderer/elements/Coords";
|
||||
|
||||
export interface Mode {
|
||||
initial: string;
|
||||
@@ -8,14 +8,9 @@ export interface Mode {
|
||||
destroy?: () => void;
|
||||
}
|
||||
|
||||
export interface MouseCoords {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Mouse {
|
||||
position: MouseCoords;
|
||||
delta: MouseCoords | null;
|
||||
position: Coords;
|
||||
delta: Coords | null;
|
||||
}
|
||||
|
||||
export interface ModeContext {
|
||||
@@ -24,11 +19,6 @@ export interface ModeContext {
|
||||
emitEvent: OnSceneChange;
|
||||
}
|
||||
|
||||
export interface Coords {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export type GeneralEventI = {
|
||||
type: "SCENE_LOAD";
|
||||
data: {};
|
||||
@@ -37,7 +27,10 @@ export type GeneralEventI = {
|
||||
export type NodeEventI =
|
||||
// Grid Events
|
||||
| {
|
||||
type: "GRID_SELECTED";
|
||||
type: "TILE_SELECTED";
|
||||
data: {
|
||||
tile: Coords;
|
||||
};
|
||||
}
|
||||
// Node Events
|
||||
| {
|
||||
|
||||
Reference in New Issue
Block a user