Skip to content

Commit

Permalink
Added canvas context menu and node/link appearance animation
Browse files Browse the repository at this point in the history
  • Loading branch information
andev0 committed Aug 20, 2024
1 parent b336578 commit c146bc7
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 31 deletions.
42 changes: 38 additions & 4 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
</svg>
<svg class="locked hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h40v-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm0-80h480v-400H240v400Zm240-120q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM360-640h240v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85v80ZM240-160v-400 400Z" />
d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h40v-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm240-200q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM360-640h240v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85v80Z" />
</svg>
</div>
</div>
Expand Down Expand Up @@ -111,12 +111,46 @@ <h2 class="title">Select a recipe</h2>
</div>

<div class="help">
<p>N - create new node</p>
<p>Ctrl + Mouse drag - move</p>
<p>Mouse drag - move node under cursor</p>
<p>Mouse wheel - zoom</p>

<p>RMB - context menu</p>

<p>N - create new node</p>
<p>L - lock canvas moving</p>

<p>Mouse drag - move node under cursor</p>
<p>LMB on slot - connect slot</p>
<p>Esc - stop connecting slots</p>

<p>Esc - cancel current operation</p>
</div>

<div id="canvas-context-menu-container" class="context-menu-container hidden">
<div class="context-menu">
<div class="option" id="create-node-option">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M440-440H240q-17 0-28.5-11.5T200-480q0-17 11.5-28.5T240-520h200v-200q0-17 11.5-28.5T480-760q17 0 28.5 11.5T520-720v200h200q17 0 28.5 11.5T760-480q0 17-11.5 28.5T720-440H520v200q0 17-11.5 28.5T480-200q-17 0-28.5-11.5T440-240v-200Z" />
</svg>
<div class="text">Create node</div>
</div>
<div class="switch" id="lock-canvas-switch">
<div class="option when-enabled">
<svg class="locked" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h40v-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm240-200q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM360-640h240v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85v80Z" />
</svg>
<div class="text">Unlock canvas</div>
</div>
<div class="option when-disabled">
<svg class="unlocked" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h360v-80q0-50-35-85t-85-35q-42 0-73.5 25.5T364-751q-4 14-16.5 22.5T320-720q-17 0-28.5-11t-8.5-26q14-69 69-116t128-47q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm0-80h480v-400H240v400Zm240-120q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM240-160v-400 400Z" />
</svg>
<div class="text">Lock canvas</div>
</div>
</div>
</div>
</div>

<script type="module" src="script.js"></script>
Expand Down
88 changes: 88 additions & 0 deletions dist/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ div.button {
background-color: var(--button-background);

border: 2px solid var(--button-border);

pointer-events: all;
}

@keyframes animate-appearance {
0% {
filter: brightness(200%);
}

100% {
filter: brightness(100%);
}
}

.animate-appearance {
animation-name: animate-appearance;
animation-timing-function: ease-in;
animation-duration: 0.7s;
}

div.button:hover {
Expand Down Expand Up @@ -127,6 +145,8 @@ svg#canvas {

width: 100%;
height: 100%;

user-select: none;
}

/* User help */
Expand Down Expand Up @@ -327,6 +347,7 @@ div.controls {

padding: 8px;
gap: 8px;
pointer-events: none;
}

.controls div.row {
Expand Down Expand Up @@ -686,3 +707,70 @@ div#node-creation-modal {

border-radius: 8px;
}

/* Context menus */

div.context-menu-container {
width: 100%;
height: 100%;

position: fixed;

cursor: default;
}

div.context-menu {
display: flex;
flex-direction: column;

min-width: 200px;

position: fixed;

border-radius: 8px;

padding: 4px;

background-color: var(--background-lv1);

user-select: none;
}

.context-menu div.option {
display: flex;

align-items: center;

gap: 8px;

border-radius: 8px;

padding: 6px;
}

.context-menu div.option:hover {
background-color: var(--background-lv2);
color: var(--foreground-accent);
cursor: default;
}

.context-menu .option svg {
align-self: stretch;

width: 20px;
height: 20px;

fill: var(--foreground);
}

.context-menu .option div.text {
flex-grow: 1;
}

.context-menu .switch:not(.enabled) div.when-enabled {
display: none;
}

.context-menu .switch.enabled div.when-disabled {
display: none;
}
99 changes: 99 additions & 0 deletions src/CanvasContextMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Point } from "./Point";

export class CanvasContextMenu extends EventTarget
{
public static readonly createNodeOptionClickedEvent = "create-node-option-clicked";
public static readonly lockCanvasSwitchClickedEvent = "lock-canvas-switch-clicked";

public constructor()
{
super();

this._menuContainer.addEventListener("mousedown", () =>
{
this.closeMenu();
});

this.setupMenuOption(this._createNodeOption, CanvasContextMenu.createNodeOptionClickedEvent);
this.setupMenuOption(this._lockCanvasSwitch, CanvasContextMenu.lockCanvasSwitchClickedEvent);
}

public addMenuTo(node: HTMLElement | SVGElement): void
{
let canvasContextMenu =
document.querySelector("#canvas-context-menu-container>.context-menu") as HTMLDivElement;

node.addEventListener("contextmenu", (event) =>
{
let mouseEvent = event as MouseEvent;

event.preventDefault();

this._openingPosition = { x: mouseEvent.clientX, y: mouseEvent.clientY };

canvasContextMenu.style.top = `${mouseEvent.pageY + 5}px`;
canvasContextMenu.style.left = `${mouseEvent.pageX + 5}px`;

this.openMenu();
});
}

public closeMenu(): void
{
this._isMenuOpened = false;

this._menuContainer.classList.add("hidden");
}

public openMenu(): void
{
this._isMenuOpened = true;

this._menuContainer.classList.remove("hidden");
}

public get isMenuOpened(): boolean
{
return this._isMenuOpened;
}

public get openingPosition(): Point | undefined
{
return this._openingPosition;
}

public setCanvasLockedSwitchEnabled(canvasLocked: boolean)
{
if (canvasLocked)
{
this._lockCanvasSwitch.classList.add("enabled");
}
else
{
this._lockCanvasSwitch.classList.remove("enabled");
}
}

private setupMenuOption(optionNode: HTMLElement, eventName: string)
{
optionNode.addEventListener("mousedown", (event) =>
{
event.stopPropagation();
});

optionNode.addEventListener("click", () =>
{
this.dispatchEvent(new Event(eventName));

this._menuContainer.classList.add("hidden");
this._isMenuOpened = false;
});
}

private _menuContainer = document.querySelector("#canvas-context-menu-container") as HTMLDivElement;
private _lockCanvasSwitch = document.querySelector("#lock-canvas-switch") as HTMLDivElement;
private _createNodeOption = document.querySelector("#create-node-option") as HTMLDivElement;

private _isMenuOpened = false;
private _openingPosition?: Point;
}
15 changes: 11 additions & 4 deletions src/MouseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ export class MouseHandler
throw Error("Slot connecting line wasn't created.");
}

const domPoint = new DOMPointReadOnly(event.clientX, event.clientY);
const svgMousePos = domPoint.matrixTransform(this.viewport.getScreenCTM()!.inverse());
const svgMousePos = MouseHandler.clientToCanvasPosition({ x: event.clientX, y: event.clientY });

this.slotConnectingCurve = Curve.fromTwoPoints(
this.slotConnectingCurve.startPoint,
Expand Down Expand Up @@ -211,8 +210,7 @@ export class MouseHandler
y: slotBounds.y + (slotBounds.height / 2)
};

const domPoint = new DOMPointReadOnly(event.clientX, event.clientY);
const svgMousePos = domPoint.matrixTransform(this.viewport.getScreenCTM()!.inverse());
const svgMousePos = MouseHandler.clientToCanvasPosition({ x: event.clientX, y: event.clientY });

this.slotConnectingCurve = Curve.fromTwoPoints(
startPos,
Expand Down Expand Up @@ -244,6 +242,15 @@ export class MouseHandler
}
}

public static clientToCanvasPosition(clientPosition: Point): Point
{
let viewport = document.querySelector("#viewport") as SVGGElement;

const domPoint = new DOMPointReadOnly(clientPosition.x, clientPosition.y);

return domPoint.matrixTransform(viewport.getScreenCTM()!.inverse());
}

private constructor()
{ }

Expand Down
2 changes: 1 addition & 1 deletion src/Sankey/SankeyLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class SankeyLink
firstSlot.addEventListener(SankeySlot.boundsChangedEvent, this.recalculate.bind(this));
secondSlot.addEventListener(SankeySlot.boundsChangedEvent, this.recalculate.bind(this));

this._svgPath = SvgFactory.createSvgPath("link");
this._svgPath = SvgFactory.createSvgPath("link", "animate-appearance");

this._resourceDisplay = this.createResourceDisplay({
id: firstSlot.resourceId,
Expand Down
5 changes: 4 additions & 1 deletion src/Sankey/SankeyNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class SankeyNode
machine: GameMachine,
)
{
this.nodeSvgGroup = SvgFactory.createSvgGroup(position, "node");
this.nodeSvgGroup = SvgFactory.createSvgGroup({
x: position.x - SankeyNode.nodeWidth / 2 - SankeySlot.slotWidth,
y: position.y - SankeyNode.nodeHeight / 2
}, "node", "animate-appearance");

this.nodeSvg = SvgFactory.createSvgRect({
width: SankeyNode.nodeWidth,
Expand Down
53 changes: 53 additions & 0 deletions src/Settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { PanZoom } from "panzoom";

export class Settings extends EventTarget
{
public static readonly isCanvasLockedChangedEvent = "canvas-locked-changed";

public static get instance()
{
return this._instance;
}

public setPanContext(panContext: PanZoom): void
{
this._panContext = panContext;
}

public get isCanvasLocked(): boolean
{
return this._isCanvasLocked;
}

public set isCanvasLocked(canvasLocked: boolean)
{
if (this._panContext == undefined)
{
throw Error("Pan context must be initialized before locking canvas");
}

if (canvasLocked)
{
this._panContext.pause();
}
else
{
this._panContext.resume();
}

this._isCanvasLocked = canvasLocked;

this.dispatchEvent(new Event(Settings.isCanvasLockedChangedEvent));
}

private constructor()
{
super();
}

private static readonly _instance = new Settings();

private _panContext?: PanZoom;

private _isCanvasLocked = false;
}
Loading

0 comments on commit c146bc7

Please sign in to comment.