Add support for rendering VueFeatures in boards
This commit is contained in:
parent
1cbe97251c
commit
c64ac82a25
4 changed files with 225 additions and 167 deletions
|
@ -1,73 +0,0 @@
|
||||||
/**
|
|
||||||
* @module
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
import { main } from "data/projEntry";
|
|
||||||
import { createCumulativeConversion } from "features/conversion";
|
|
||||||
import { jsx } from "features/feature";
|
|
||||||
import { createHotkey } from "features/hotkey";
|
|
||||||
import { createReset } from "features/reset";
|
|
||||||
import MainDisplay from "features/resources/MainDisplay.vue";
|
|
||||||
import { createResource } from "features/resources/resource";
|
|
||||||
import { addTooltip } from "features/tooltips/tooltip";
|
|
||||||
import { createResourceTooltip } from "features/trees/tree";
|
|
||||||
import { BaseLayer, createLayer } from "game/layers";
|
|
||||||
import type { DecimalSource } from "util/bignum";
|
|
||||||
import { render } from "util/vue";
|
|
||||||
import { createLayerTreeNode, createResetButton } from "../common";
|
|
||||||
|
|
||||||
const id = "p";
|
|
||||||
const layer = createLayer(id, function (this: BaseLayer) {
|
|
||||||
const name = "Prestige";
|
|
||||||
const color = "#4BDC13";
|
|
||||||
const points = createResource<DecimalSource>(0, "prestige points");
|
|
||||||
|
|
||||||
const conversion = createCumulativeConversion(() => ({
|
|
||||||
formula: x => x.div(10).sqrt(),
|
|
||||||
baseResource: main.points,
|
|
||||||
gainResource: points
|
|
||||||
}));
|
|
||||||
|
|
||||||
const reset = createReset(() => ({
|
|
||||||
thingsToReset: (): Record<string, unknown>[] => [layer]
|
|
||||||
}));
|
|
||||||
|
|
||||||
const treeNode = createLayerTreeNode(() => ({
|
|
||||||
layerID: id,
|
|
||||||
color,
|
|
||||||
reset
|
|
||||||
}));
|
|
||||||
const tooltip = addTooltip(treeNode, {
|
|
||||||
display: createResourceTooltip(points),
|
|
||||||
pinnable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const resetButton = createResetButton(() => ({
|
|
||||||
conversion,
|
|
||||||
tree: main.tree,
|
|
||||||
treeNode
|
|
||||||
}));
|
|
||||||
|
|
||||||
const hotkey = createHotkey(() => ({
|
|
||||||
description: "Reset for prestige points",
|
|
||||||
key: "p",
|
|
||||||
onPress: resetButton.onClick
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
points,
|
|
||||||
tooltip,
|
|
||||||
display: jsx(() => (
|
|
||||||
<>
|
|
||||||
<MainDisplay resource={points} color={color} />
|
|
||||||
{render(resetButton)}
|
|
||||||
</>
|
|
||||||
)),
|
|
||||||
treeNode,
|
|
||||||
hotkey
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default layer;
|
|
|
@ -4,6 +4,7 @@ import SVGNode from "features/boards/SVGNode.vue";
|
||||||
import SquareProgress from "features/boards/SquareProgress.vue";
|
import SquareProgress from "features/boards/SquareProgress.vue";
|
||||||
import {
|
import {
|
||||||
NodePosition,
|
NodePosition,
|
||||||
|
makeDraggable,
|
||||||
placeInAvailableSpace,
|
placeInAvailableSpace,
|
||||||
setupActions,
|
setupActions,
|
||||||
setupDraggableNode,
|
setupDraggableNode,
|
||||||
|
@ -11,24 +12,29 @@ import {
|
||||||
setupUniqueIds
|
setupUniqueIds
|
||||||
} from "features/boards/board";
|
} from "features/boards/board";
|
||||||
import { jsx } from "features/feature";
|
import { jsx } from "features/feature";
|
||||||
|
import { createResource } from "features/resources/resource";
|
||||||
|
import { createUpgrade } from "features/upgrades/upgrade";
|
||||||
import type { BaseLayer, GenericLayer } from "game/layers";
|
import type { BaseLayer, GenericLayer } from "game/layers";
|
||||||
import { createLayer } from "game/layers";
|
import { createLayer } from "game/layers";
|
||||||
import { persistent } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import type { Player } from "game/player";
|
import type { Player } from "game/player";
|
||||||
|
import { createCostRequirement } from "game/requirements";
|
||||||
|
import { render } from "util/vue";
|
||||||
import { ComponentPublicInstance, computed, ref, watch } from "vue";
|
import { ComponentPublicInstance, computed, ref, watch } from "vue";
|
||||||
import prestige from "./layers/prestige";
|
import "./common.css";
|
||||||
|
|
||||||
type ANode = NodePosition & { id: number; links: number[]; type: "anode" };
|
|
||||||
type BNode = NodePosition & { id: number; links: number[]; type: "bnode" };
|
|
||||||
type NodeTypes = ANode | BNode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
export const main = createLayer("main", function (this: BaseLayer) {
|
export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
|
type ANode = NodePosition & { id: number; links: number[]; type: "anode" };
|
||||||
|
type BNode = NodePosition & { id: number; links: number[]; type: "bnode" };
|
||||||
|
type CNode = typeof cNode & { position: Persistent<NodePosition> };
|
||||||
|
type NodeTypes = ANode | BNode;
|
||||||
|
|
||||||
const board = ref<ComponentPublicInstance<typeof Board>>();
|
const board = ref<ComponentPublicInstance<typeof Board>>();
|
||||||
|
|
||||||
const { select, deselect, selected } = setupSelectable<NodeTypes>();
|
const { select, deselect, selected } = setupSelectable<number>();
|
||||||
const {
|
const {
|
||||||
select: selectAction,
|
select: selectAction,
|
||||||
deselect: deselectAction,
|
deselect: deselectAction,
|
||||||
|
@ -50,10 +56,15 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
receivingNodes,
|
receivingNodes,
|
||||||
receivingNode,
|
receivingNode,
|
||||||
dragDelta
|
dragDelta
|
||||||
} = setupDraggableNode<NodeTypes /* | typeof cNode*/>({
|
} = setupDraggableNode<number | "cnode">({
|
||||||
board,
|
board,
|
||||||
isDraggable: function (node) {
|
getPosition(id) {
|
||||||
return nodes.value.includes(node);
|
return nodesById.value[id] ?? (cNode as CNode).position.value;
|
||||||
|
},
|
||||||
|
setPosition(id, position) {
|
||||||
|
const node = nodesById.value[id] ?? (cNode as CNode).position.value;
|
||||||
|
node.x = position.x;
|
||||||
|
node.y = position.y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,26 +75,26 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
// c node also exists but is a single Upgrade element that cannot be selected, but can be dragged
|
// c node also exists but is a single Upgrade element that cannot be selected, but can be dragged
|
||||||
// d nodes are a performance test - 1000 simple nodes that have no interactions
|
// d nodes are a performance test - 1000 simple nodes that have no interactions
|
||||||
// Make all nodes animate in (decorator? `fadeIn(feature)?)
|
// Make all nodes animate in (decorator? `fadeIn(feature)?)
|
||||||
const nodes = persistent<NodeTypes[]>([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]);
|
const nodes = persistent<(ANode | BNode)[]>([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]);
|
||||||
const nodesById = computed<Record<string, NodeTypes>>(() =>
|
const nodesById = computed<Record<string, NodeTypes>>(() =>
|
||||||
nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
||||||
);
|
);
|
||||||
function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
||||||
if (nodeBeingDragged.value == null) {
|
if (nodeBeingDragged.value == null) {
|
||||||
startDrag(e, node);
|
startDrag(e, node.id);
|
||||||
}
|
}
|
||||||
deselect();
|
deselect();
|
||||||
}
|
}
|
||||||
function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
||||||
if (!hasDragged.value) {
|
if (!hasDragged.value) {
|
||||||
endDrag();
|
endDrag();
|
||||||
select(node);
|
if (typeof node.id === "number") {
|
||||||
|
select(node.id);
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getTranslateString(node: NodePosition, overrideSelected?: boolean) {
|
function getTranslateString(node: NodePosition, isDragging: boolean) {
|
||||||
const isSelected = overrideSelected == null ? selected.value === node : overrideSelected;
|
|
||||||
const isDragging = !isSelected && nodeBeingDragged.value === node;
|
|
||||||
let x = node.x;
|
let x = node.x;
|
||||||
let y = node.y;
|
let y = node.y;
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
|
@ -95,13 +106,13 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
function getRotateString(rotation: number) {
|
function getRotateString(rotation: number) {
|
||||||
return ` rotate(${rotation}deg) `;
|
return ` rotate(${rotation}deg) `;
|
||||||
}
|
}
|
||||||
function getScaleString(node: NodePosition, overrideSelected?: boolean) {
|
function getScaleString(nodeOrBool: NodeTypes | boolean) {
|
||||||
const isSelected = overrideSelected == null ? selected.value === node : overrideSelected;
|
const isSelected =
|
||||||
|
typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id;
|
||||||
return isSelected ? " scale(1.2)" : "";
|
return isSelected ? " scale(1.2)" : "";
|
||||||
}
|
}
|
||||||
function getOpacityString(node: NodePosition, overrideSelected?: boolean) {
|
function getOpacityString(node: NodeTypes) {
|
||||||
const isSelected = overrideSelected == null ? selected.value === node : overrideSelected;
|
const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id;
|
||||||
const isDragging = !isSelected && nodeBeingDragged.value === node;
|
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
return "; opacity: 0.5;";
|
return "; opacity: 0.5;";
|
||||||
}
|
}
|
||||||
|
@ -111,16 +122,19 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
const renderANode = function (node: ANode) {
|
const renderANode = function (node: ANode) {
|
||||||
return (
|
return (
|
||||||
<SVGNode
|
<SVGNode
|
||||||
style={`transform: ${getTranslateString(node)}${getOpacityString(node)}`}
|
style={`transform: ${getTranslateString(
|
||||||
|
node,
|
||||||
|
selected.value === node.id && nodeBeingDragged.value === node.id
|
||||||
|
)}${getOpacityString(node)}`}
|
||||||
onMouseDown={e => mouseDownNode(e, node)}
|
onMouseDown={e => mouseDownNode(e, node)}
|
||||||
onMouseUp={e => mouseUpNode(e, node)}
|
onMouseUp={e => mouseUpNode(e, node)}
|
||||||
>
|
>
|
||||||
<g style={`transform: ${getScaleString(node)}; transition-duration: 0s`}>
|
<g style={`transform: ${getScaleString(node)}`}>
|
||||||
{receivingNodes.value.includes(node) && (
|
{receivingNodes.value.includes(node.id) && (
|
||||||
<circle
|
<circle
|
||||||
r="58"
|
r="58"
|
||||||
fill="var(--background)"
|
fill="var(--background)"
|
||||||
stroke={receivingNode.value === node ? "#0F0" : "#0F03"}
|
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -132,7 +146,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
stroke-width="4"
|
stroke-width="4"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
{selected.value === node && selectedAction.value === 0 && (
|
{selected.value === node.id && selectedAction.value === 0 && (
|
||||||
<text y="140" fill="var(--foreground)" class="node-text">
|
<text y="140" fill="var(--foreground)" class="node-text">
|
||||||
Spawn B Node
|
Spawn B Node
|
||||||
</text>
|
</text>
|
||||||
|
@ -144,8 +158,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const aActions = setupActions({
|
const aActions = setupActions({
|
||||||
node: selected,
|
node: () => nodesById.value[selected.value ?? ""],
|
||||||
shouldShowActions: () => selected.value?.type === "anode",
|
shouldShowActions: node => node.type === "anode",
|
||||||
actions(node) {
|
actions(node) {
|
||||||
return [
|
return [
|
||||||
p => (
|
p => (
|
||||||
|
@ -153,10 +167,10 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
style={`transform: ${getTranslateString(
|
style={`transform: ${getTranslateString(
|
||||||
p,
|
p,
|
||||||
selectedAction.value === 0
|
selectedAction.value === 0
|
||||||
)}${getScaleString(p, selectedAction.value === 0)}`}
|
)}${getScaleString(selectedAction.value === 0)}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedAction.value === 0) {
|
if (selectedAction.value === 0) {
|
||||||
spawnBNode(node);
|
spawnBNode(node as ANode);
|
||||||
} else {
|
} else {
|
||||||
selectAction(0);
|
selectAction(0);
|
||||||
}
|
}
|
||||||
|
@ -176,16 +190,15 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
const renderBNode = function (node: BNode) {
|
const renderBNode = function (node: BNode) {
|
||||||
return (
|
return (
|
||||||
<SVGNode
|
<SVGNode
|
||||||
style={`transform: ${getTranslateString(node)}${getOpacityString(node)}`}
|
style={`transform: ${getTranslateString(
|
||||||
|
node,
|
||||||
|
selected.value === node.id && nodeBeingDragged.value === node.id
|
||||||
|
)}${getOpacityString(node)}`}
|
||||||
onMouseDown={e => mouseDownNode(e, node)}
|
onMouseDown={e => mouseDownNode(e, node)}
|
||||||
onMouseUp={e => mouseUpNode(e, node)}
|
onMouseUp={e => mouseUpNode(e, node)}
|
||||||
>
|
>
|
||||||
<g
|
<g style={`transform: ${getScaleString(node)}${getRotateString(45)}`}>
|
||||||
style={`transform: ${getScaleString(node)}${getRotateString(
|
{receivingNodes.value.includes(node.id) && (
|
||||||
45
|
|
||||||
)}; transition-duration: 0s`}
|
|
||||||
>
|
|
||||||
{receivingNodes.value.includes(node) && (
|
|
||||||
<rect
|
<rect
|
||||||
width={50 * sqrtTwo + 16}
|
width={50 * sqrtTwo + 16}
|
||||||
height={50 * sqrtTwo + 16}
|
height={50 * sqrtTwo + 16}
|
||||||
|
@ -193,7 +206,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
(-50 * sqrtTwo + 16) / 2
|
(-50 * sqrtTwo + 16) / 2
|
||||||
})`}
|
})`}
|
||||||
fill="var(--background)"
|
fill="var(--background)"
|
||||||
stroke={receivingNode.value === node ? "#0F0" : "#0F03"}
|
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -213,7 +226,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
stroke-width="4"
|
stroke-width="4"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
{selected.value === node && selectedAction.value === 0 && (
|
{selected.value === node.id && selectedAction.value === 0 && (
|
||||||
<text y="140" fill="var(--foreground)" class="node-text">
|
<text y="140" fill="var(--foreground)" class="node-text">
|
||||||
Spawn A Node
|
Spawn A Node
|
||||||
</text>
|
</text>
|
||||||
|
@ -225,8 +238,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const bActions = setupActions({
|
const bActions = setupActions({
|
||||||
node: selected,
|
node: () => nodesById.value[selected.value ?? ""],
|
||||||
shouldShowActions: () => selected.value?.type === "bnode",
|
shouldShowActions: node => node.type === "bnode",
|
||||||
actions(node) {
|
actions(node) {
|
||||||
return [
|
return [
|
||||||
p => (
|
p => (
|
||||||
|
@ -234,10 +247,10 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
style={`transform: ${getTranslateString(
|
style={`transform: ${getTranslateString(
|
||||||
p,
|
p,
|
||||||
selectedAction.value === 0
|
selectedAction.value === 0
|
||||||
)}${getScaleString(p, selectedAction.value === 0)}`}
|
)}${getScaleString(selectedAction.value === 0)}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedAction.value === 0) {
|
if (selectedAction.value === 0) {
|
||||||
spawnANode(node);
|
spawnANode(node as BNode);
|
||||||
} else {
|
} else {
|
||||||
selectAction(0);
|
selectAction(0);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +266,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
},
|
},
|
||||||
distance: 100
|
distance: 100
|
||||||
});
|
});
|
||||||
function spawnANode(parent: NodeTypes) {
|
function spawnANode(parent: ANode | BNode) {
|
||||||
const node: ANode = {
|
const node: ANode = {
|
||||||
x: parent.x,
|
x: parent.x,
|
||||||
y: parent.y,
|
y: parent.y,
|
||||||
|
@ -264,7 +277,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
placeInAvailableSpace(node, nodes.value);
|
placeInAvailableSpace(node, nodes.value);
|
||||||
nodes.value.push(node);
|
nodes.value.push(node);
|
||||||
}
|
}
|
||||||
function spawnBNode(parent: NodeTypes) {
|
function spawnBNode(parent: ANode | BNode) {
|
||||||
const node: BNode = {
|
const node: BNode = {
|
||||||
x: parent.x,
|
x: parent.x,
|
||||||
y: parent.y,
|
y: parent.y,
|
||||||
|
@ -276,14 +289,29 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
nodes.value.push(node);
|
nodes.value.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// const cNode = createUpgrade(() => ({
|
const points = createResource(10);
|
||||||
// requirements: createCostRequirement(() => ({ cost: 10, resource: points })),
|
const cNode = createUpgrade(() => ({
|
||||||
// style: {
|
display: "<h1>C</h1>",
|
||||||
// x: "100px",
|
// Purposefully not using noPersist
|
||||||
// y: "100px"
|
requirements: createCostRequirement(() => ({ cost: 10, resource: points })),
|
||||||
// }
|
style: {
|
||||||
// }));
|
x: "100px",
|
||||||
// makeDraggable(cNode); // TODO make decorator
|
y: "100px"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
makeDraggable(cNode, {
|
||||||
|
id: "cnode",
|
||||||
|
endDrag,
|
||||||
|
startDrag,
|
||||||
|
hasDragged,
|
||||||
|
nodeBeingDragged,
|
||||||
|
dragDelta,
|
||||||
|
onMouseUp() {
|
||||||
|
if (!hasDragged.value) {
|
||||||
|
cNode.purchase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// const dNodes;
|
// const dNodes;
|
||||||
|
|
||||||
|
@ -302,22 +330,22 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
stroke="white"
|
stroke="white"
|
||||||
stroke-width={4}
|
stroke-width={4}
|
||||||
x1={
|
x1={
|
||||||
nodeBeingDragged.value === link.from
|
nodeBeingDragged.value === link.from.id
|
||||||
? dragDelta.value.x + link.from.x
|
? dragDelta.value.x + link.from.x
|
||||||
: link.from.x
|
: link.from.x
|
||||||
}
|
}
|
||||||
y1={
|
y1={
|
||||||
nodeBeingDragged.value === link.from
|
nodeBeingDragged.value === link.from.id
|
||||||
? dragDelta.value.y + link.from.y
|
? dragDelta.value.y + link.from.y
|
||||||
: link.from.y
|
: link.from.y
|
||||||
}
|
}
|
||||||
x2={
|
x2={
|
||||||
nodeBeingDragged.value === link.to
|
nodeBeingDragged.value === link.to.id
|
||||||
? dragDelta.value.x + link.to.x
|
? dragDelta.value.x + link.to.x
|
||||||
: link.to.x
|
: link.to.x
|
||||||
}
|
}
|
||||||
y2={
|
y2={
|
||||||
nodeBeingDragged.value === link.to
|
nodeBeingDragged.value === link.to.id
|
||||||
? dragDelta.value.y + link.to.y
|
? dragDelta.value.y + link.to.y
|
||||||
: link.to.y
|
: link.to.y
|
||||||
}
|
}
|
||||||
|
@ -328,22 +356,30 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
|
|
||||||
const nextId = setupUniqueIds(() => nodes.value);
|
const nextId = setupUniqueIds(() => nodes.value);
|
||||||
|
|
||||||
function filterNodes(n: NodeTypes) {
|
function filterNodes(n: number | "cnode") {
|
||||||
return n !== nodeBeingDragged.value && n !== selected.value;
|
return n !== nodeBeingDragged.value && n !== selected.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(node: NodeTypes | undefined) {
|
function renderNodeById(id: number | "cnode" | undefined) {
|
||||||
if (node == undefined) {
|
if (id == null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else if (node.type === "anode") {
|
}
|
||||||
|
return renderNode(nodesById.value[id] ?? cNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNode(node: NodeTypes | typeof cNode) {
|
||||||
|
if (node.type === "anode") {
|
||||||
return renderANode(node);
|
return renderANode(node);
|
||||||
} else if (node.type === "bnode") {
|
} else if (node.type === "bnode") {
|
||||||
return renderBNode(node);
|
return renderBNode(node);
|
||||||
|
} else {
|
||||||
|
return render(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "Tree",
|
name: "Tree",
|
||||||
|
color: "var(--accent1)",
|
||||||
display: jsx(() => (
|
display: jsx(() => (
|
||||||
<>
|
<>
|
||||||
<Board
|
<Board
|
||||||
|
@ -354,18 +390,20 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
ref={board}
|
ref={board}
|
||||||
>
|
>
|
||||||
<SVGNode>{links()}</SVGNode>
|
<SVGNode>{links()}</SVGNode>
|
||||||
{nodes.value.filter(filterNodes).map(renderNode)}
|
{nodes.value.filter(n => filterNodes(n.id)).map(renderNode)}
|
||||||
|
{filterNodes("cnode") && render(cNode)}
|
||||||
<SVGNode>
|
<SVGNode>
|
||||||
{aActions()}
|
{aActions()}
|
||||||
{bActions()}
|
{bActions()}
|
||||||
</SVGNode>
|
</SVGNode>
|
||||||
{renderNode(selected.value)}
|
{renderNodeById(selected.value)}
|
||||||
{renderNode(nodeBeingDragged.value)}
|
{renderNodeById(nodeBeingDragged.value)}
|
||||||
</Board>
|
</Board>
|
||||||
</>
|
</>
|
||||||
)),
|
)),
|
||||||
boardNodes: nodes
|
boardNodes: nodes,
|
||||||
// cNode
|
cNode,
|
||||||
|
selected: persistent(selected)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -376,7 +414,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
export const getInitialLayers = (
|
export const getInitialLayers = (
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
player: Partial<Player>
|
player: Partial<Player>
|
||||||
): Array<GenericLayer> => [main, prestige];
|
): Array<GenericLayer> => [main];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A computed ref whose value is true whenever the game is over.
|
* A computed ref whose value is true whenever the game is over.
|
||||||
|
|
37
src/features/boards/Draggable.vue
Normal file
37
src/features/boards/Draggable.vue
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:style="`transform: translate(calc(${unref(position).x}px - 50%), ${unref(position).y}px);`"
|
||||||
|
@mousedown="e => mouseDown(e)"
|
||||||
|
@touchstart.passive="e => mouseDown(e)"
|
||||||
|
@mouseup="e => mouseUp(e)"
|
||||||
|
@touchend.passive="e => mouseUp(e)"
|
||||||
|
>
|
||||||
|
<component v-if="comp" :is="comp" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { jsx } from "features/feature";
|
||||||
|
import { VueFeature, coerceComponent, renderJSX } from "util/vue";
|
||||||
|
import { Ref, shallowRef, unref } from "vue";
|
||||||
|
import { NodePosition } from "./board";
|
||||||
|
unref;
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
element: VueFeature;
|
||||||
|
mouseDown: (e: MouseEvent | TouchEvent) => void;
|
||||||
|
mouseUp: (e: MouseEvent | TouchEvent) => void;
|
||||||
|
position: Ref<NodePosition>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const comp = shallowRef(coerceComponent(jsx(() => renderJSX(props.element))));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,15 @@
|
||||||
import Board from "features/boards/Board.vue";
|
import Board from "features/boards/Board.vue";
|
||||||
import { jsx } from "features/feature";
|
import Draggable from "features/boards/Draggable.vue";
|
||||||
|
import { Component, GatherProps, GenericComponent, jsx } from "features/feature";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import type { PanZoom } from "panzoom";
|
import type { PanZoom } from "panzoom";
|
||||||
import { Direction, isFunction } from "util/common";
|
import { Direction, isFunction } from "util/common";
|
||||||
import type { Computable, ProcessedComputable } from "util/computed";
|
import type { Computable, ProcessedComputable } from "util/computed";
|
||||||
import { convertComputable } from "util/computed";
|
import { convertComputable } from "util/computed";
|
||||||
|
import { VueFeature } from "util/vue";
|
||||||
import type { ComponentPublicInstance, Ref } from "vue";
|
import type { ComponentPublicInstance, Ref } from "vue";
|
||||||
import { computed, ref, unref, watchEffect } from "vue";
|
import { computed, nextTick, ref, unref, watchEffect } from "vue";
|
||||||
import panZoom from "vue-panzoom";
|
import panZoom from "vue-panzoom";
|
||||||
|
|
||||||
globalBus.on("setupVue", app => panZoom.install(app));
|
globalBus.on("setupVue", app => panZoom.install(app));
|
||||||
|
@ -53,20 +56,20 @@ export function setupSelectable<T>() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupDraggableNode<T extends NodePosition, S extends NodePosition = T>(options: {
|
export function setupDraggableNode<T>(options: {
|
||||||
board: Ref<ComponentPublicInstance<typeof Board> | undefined>;
|
board: Ref<ComponentPublicInstance<typeof Board> | undefined>;
|
||||||
receivingNodes?: NodeComputable<T, S[]>;
|
getPosition: (node: T) => NodePosition;
|
||||||
dropAreaRadius?: NodeComputable<S, number>;
|
setPosition: (node: T, position: NodePosition) => void;
|
||||||
isDraggable?: NodeComputable<T, boolean>;
|
receivingNodes?: NodeComputable<T, T[]>;
|
||||||
onDrop?: (acceptingNode: S, draggingNode: T) => void;
|
dropAreaRadius?: NodeComputable<T, number>;
|
||||||
|
onDrop?: (acceptingNode: T, draggingNode: T) => void;
|
||||||
}) {
|
}) {
|
||||||
const nodeBeingDragged = ref<T>();
|
const nodeBeingDragged = ref<T>();
|
||||||
const receivingNode = ref<S>();
|
const receivingNode = ref<T>();
|
||||||
const hasDragged = ref(false);
|
const hasDragged = ref(false);
|
||||||
const mousePosition = ref<NodePosition>();
|
const mousePosition = ref<NodePosition>();
|
||||||
const lastMousePosition = ref({ x: 0, y: 0 });
|
const lastMousePosition = ref({ x: 0, y: 0 });
|
||||||
const dragDelta = ref({ x: 0, y: 0 });
|
const dragDelta = ref({ x: 0, y: 0 });
|
||||||
const isDraggable = options.isDraggable ?? true;
|
|
||||||
const receivingNodes = computed(() =>
|
const receivingNodes = computed(() =>
|
||||||
nodeBeingDragged.value == null
|
nodeBeingDragged.value == null
|
||||||
? []
|
? []
|
||||||
|
@ -75,31 +78,26 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
||||||
);
|
);
|
||||||
const dropAreaRadius = options.dropAreaRadius ?? 50;
|
const dropAreaRadius = options.dropAreaRadius ?? 50;
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (nodeBeingDragged.value != null && !unwrapNodeRef(isDraggable, nodeBeingDragged.value)) {
|
|
||||||
result.endDrag();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const node = nodeBeingDragged.value;
|
const node = nodeBeingDragged.value;
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const originalPosition = options.getPosition(node);
|
||||||
const position = {
|
const position = {
|
||||||
x: node.x + dragDelta.value.x,
|
x: originalPosition.x + dragDelta.value.x,
|
||||||
y: node.y + dragDelta.value.y
|
y: originalPosition.y + dragDelta.value.y
|
||||||
};
|
};
|
||||||
let smallestDistance = Number.MAX_VALUE;
|
let smallestDistance = Number.MAX_VALUE;
|
||||||
|
|
||||||
receivingNode.value = unref(receivingNodes).reduce((smallest: S | undefined, curr: S) => {
|
receivingNode.value = unref(receivingNodes).reduce((smallest: T | undefined, curr: T) => {
|
||||||
if ((curr as S | T) === node) {
|
if ((curr as T) === node) {
|
||||||
return smallest;
|
return smallest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const distanceSquared =
|
const { x, y } = options.getPosition(curr);
|
||||||
Math.pow(position.x - curr.x, 2) + Math.pow(position.y - curr.y, 2);
|
const distanceSquared = Math.pow(position.x - x, 2) + Math.pow(position.y - y, 2);
|
||||||
const size = unwrapNodeRef(dropAreaRadius, curr);
|
const size = unwrapNodeRef(dropAreaRadius, curr);
|
||||||
if (distanceSquared > smallestDistance || distanceSquared > size * size) {
|
if (distanceSquared > smallestDistance || distanceSquared > size * size) {
|
||||||
return smallest;
|
return smallest;
|
||||||
|
@ -118,7 +116,7 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
||||||
lastMousePosition,
|
lastMousePosition,
|
||||||
dragDelta,
|
dragDelta,
|
||||||
receivingNodes,
|
receivingNodes,
|
||||||
startDrag: function (e: MouseEvent | TouchEvent, node?: T) {
|
startDrag: function (e: MouseEvent | TouchEvent, node: T) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
@ -141,17 +139,17 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
||||||
dragDelta.value = { x: 0, y: 0 };
|
dragDelta.value = { x: 0, y: 0 };
|
||||||
hasDragged.value = false;
|
hasDragged.value = false;
|
||||||
|
|
||||||
if (node != null && unwrapNodeRef(isDraggable, node)) {
|
|
||||||
nodeBeingDragged.value = node;
|
nodeBeingDragged.value = node;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
endDrag: function () {
|
endDrag: function () {
|
||||||
if (nodeBeingDragged.value == null) {
|
if (nodeBeingDragged.value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (receivingNode.value == null) {
|
if (receivingNode.value == null) {
|
||||||
nodeBeingDragged.value.x += Math.round(dragDelta.value.x / 25) * 25;
|
const { x, y } = options.getPosition(nodeBeingDragged.value);
|
||||||
nodeBeingDragged.value.y += Math.round(dragDelta.value.y / 25) * 25;
|
const newX = x + Math.round(dragDelta.value.x / 25) * 25;
|
||||||
|
const newY = y + Math.round(dragDelta.value.y / 25) * 25;
|
||||||
|
options.setPosition(nodeBeingDragged.value, { x: newX, y: newY });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receivingNode.value != null) {
|
if (receivingNode.value != null) {
|
||||||
|
@ -210,6 +208,64 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeDraggable<T extends VueFeature, S>(
|
||||||
|
element: T,
|
||||||
|
options: {
|
||||||
|
id: S;
|
||||||
|
nodeBeingDragged: Ref<S | undefined>;
|
||||||
|
hasDragged: Ref<boolean>;
|
||||||
|
dragDelta: Ref<NodePosition>;
|
||||||
|
startDrag: (e: MouseEvent | TouchEvent, id: S) => void;
|
||||||
|
endDrag: VoidFunction;
|
||||||
|
onMouseDown?: (e: MouseEvent | TouchEvent) => boolean | void;
|
||||||
|
onMouseUp?: (e: MouseEvent | TouchEvent) => boolean | void;
|
||||||
|
initialPosition?: NodePosition;
|
||||||
|
}
|
||||||
|
): asserts element is T & { position: Persistent<NodePosition> } {
|
||||||
|
const position = persistent(options.initialPosition ?? { x: 0, y: 0 });
|
||||||
|
(element as T & { position: Persistent<NodePosition> }).position = position;
|
||||||
|
const computedPosition = computed(() => {
|
||||||
|
if (options.nodeBeingDragged.value === options.id) {
|
||||||
|
return {
|
||||||
|
x: position.value.x + options.dragDelta.value.x,
|
||||||
|
y: position.value.y + options.dragDelta.value.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return position.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleMouseDown(e: MouseEvent | TouchEvent) {
|
||||||
|
if (options.onMouseDown?.(e) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.nodeBeingDragged.value == null) {
|
||||||
|
options.startDrag(e, options.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseUp(e: MouseEvent | TouchEvent) {
|
||||||
|
options.onMouseUp?.(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const elementComponent = element[Component];
|
||||||
|
const elementGatherProps = element[GatherProps].bind(element);
|
||||||
|
element[Component] = Draggable as GenericComponent;
|
||||||
|
element[GatherProps] = function gatherTooltipProps(this: typeof options) {
|
||||||
|
return {
|
||||||
|
element: {
|
||||||
|
[Component]: elementComponent,
|
||||||
|
[GatherProps]: elementGatherProps
|
||||||
|
},
|
||||||
|
mouseDown: handleMouseDown,
|
||||||
|
mouseUp: handleMouseUp,
|
||||||
|
position: computedPosition
|
||||||
|
};
|
||||||
|
}.bind(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function setupActions<T extends NodePosition>(options: {
|
export function setupActions<T extends NodePosition>(options: {
|
||||||
node: Computable<T | undefined>;
|
node: Computable<T | undefined>;
|
||||||
shouldShowActions?: NodeComputable<T, boolean>;
|
shouldShowActions?: NodeComputable<T, boolean>;
|
||||||
|
|
Loading…
Reference in a new issue