Profectus/src/components/board/BoardNode.vue

182 lines
5.3 KiB
Vue
Raw Normal View History

<template>
<g
class="boardnode"
:style="{ opacity: dragging?.id === node.id ? 0.5 : 1 }"
:transform="`translate(${position.x},${position.y})`"
@mouseenter="mouseEnter"
@mouseleave="mouseLeave"
@mousedown="e => $emit('startDragging', e, node.id)"
>
<circle
v-if="canAccept"
:r="size + 8"
:fill="backgroundColor"
:stroke="receivingNode ? '#0F0' : '#0F03'"
:stroke-width="2"
/>
<circle :r="size" :fill="fillColor" :stroke="outlineColor" :stroke-width="4" />
<circle
v-if="progressDisplay === ProgressDisplay.Fill"
:r="size * progress"
:fill="progressColor"
/>
<circle
v-else
:r="size + 4.5"
class="progressRing"
fill="transparent"
:stroke-dasharray="(size + 4.5) * 2 * Math.PI"
:stroke-width="5"
:stroke-dashoffset="(size + 4.5) * 2 * Math.PI - progress * (size + 4.5) * 2 * Math.PI"
:stroke="progressColor"
/>
<text :fill="titleColor" class="node-title">{{ title }}</text>
</g>
</template>
<script lang="ts">
import themes from "@/data/themes";
import { ProgressDisplay } from "@/game/enums";
import player from "@/game/player";
import { BoardNode, NodeType } from "@/typings/features/board";
import { getNodeTypeProperty } from "@/util/features";
import { InjectLayerMixin } from "@/util/vue";
import { defineComponent, PropType } from "vue";
export default defineComponent({
name: "BoardNode",
mixins: [InjectLayerMixin],
data() {
return {
ProgressDisplay,
lastMousePosition: { x: 0, y: 0 },
hovering: false
};
},
emits: ["startDragging", "endDragging"],
props: {
node: {
type: Object as PropType<BoardNode>,
required: true
},
nodeType: {
type: Object as PropType<NodeType>,
required: true
},
dragging: {
type: Object as PropType<BoardNode>
},
dragged: {
type: Object as PropType<{ x: number; y: number }>,
required: true
},
receivingNode: {
type: Boolean,
default: false
}
},
computed: {
draggable(): boolean {
return getNodeTypeProperty(this.nodeType, this.node, "draggable");
},
position(): { x: number; y: number } {
return this.draggable && this.dragging?.id === this.node.id
? {
x: this.node.position.x + Math.round(this.dragged.x / 25) * 25,
y: this.node.position.y + Math.round(this.dragged.y / 25) * 25
}
: this.node.position;
},
size(): number {
let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
if (this.receivingNode) {
size *= 1.25;
} else if (this.hovering) {
size *= 1.15;
}
return size;
},
title(): string {
return getNodeTypeProperty(this.nodeType, this.node, "title");
},
progress(): number {
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
},
backgroundColor(): string {
return themes[player.theme].variables["--background"];
},
outlineColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "outlineColor") ||
themes[player.theme].variables["--separator"]
);
},
fillColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "fillColor") ||
themes[player.theme].variables["--secondary-background"]
);
},
progressColor(): string {
return getNodeTypeProperty(this.nodeType, this.node, "progressColor") || "none";
},
titleColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "titleColor") ||
themes[player.theme].variables["--color"]
);
},
progressDisplay(): ProgressDisplay {
return (
getNodeTypeProperty(this.nodeType, this.node, "progressDisplay") ||
ProgressDisplay.Outline
);
},
canAccept(): boolean {
if (this.dragging == null) {
return false;
}
return typeof this.nodeType.canAccept === "boolean"
? this.nodeType.canAccept
: this.nodeType.canAccept(this.node, this.dragging);
}
},
methods: {
mouseEnter() {
this.hovering = true;
},
mouseLeave() {
this.hovering = false;
}
},
watch: {
onDraggableChanged() {
if (this.dragging && !this.draggable) {
this.$emit("endDragging", this.node.id);
}
}
}
});
</script>
<style scoped>
.boardnode {
cursor: pointer;
transition-duration: 0s;
}
.node-title {
text-anchor: middle;
dominant-baseline: middle;
font-family: monospace;
font-size: 200%;
}
.progressRing {
transform: rotate(-90deg);
}
</style>