mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
fix: strange cursor behaviour when zoomed out
This commit is contained in:
@@ -8,7 +8,10 @@ interface Props {
|
||||
}
|
||||
|
||||
const COLOR = "grey.900";
|
||||
const SIZE = 11;
|
||||
const ARROW = {
|
||||
size: 11,
|
||||
top: 8,
|
||||
};
|
||||
const ANIMATIONS = {
|
||||
in: keyframes`
|
||||
0% {
|
||||
@@ -27,21 +30,21 @@ export const ContextMenu = ({ position, children }: Props) => {
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: position.y,
|
||||
left: position.x + SIZE,
|
||||
top: position.y - 20,
|
||||
left: position.x + ARROW.size * 2,
|
||||
animation: `${ANIMATIONS.in} 0.2s ease-in-out`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: -(SIZE - 2),
|
||||
top: 8,
|
||||
left: -(ARROW.size - 2),
|
||||
top: ARROW.top,
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderTop: `${SIZE}px solid transparent`,
|
||||
borderBottom: `${SIZE}px solid transparent`,
|
||||
borderRight: `${SIZE}px solid`,
|
||||
borderTop: `${ARROW.size}px solid transparent`,
|
||||
borderBottom: `${ARROW.size}px solid transparent`,
|
||||
borderRight: `${ARROW.size}px solid`,
|
||||
borderRightColor: COLOR,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React, { useRef, useEffect, useContext } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Renderer } from "../renderer/Renderer";
|
||||
import { useGlobalState } from "../hooks/useGlobalState";
|
||||
import { useMouseInput } from "../hooks/useMouseInput";
|
||||
import { modeManagerContext } from "../contexts/ModeManagerContext";
|
||||
import { Select } from "../modes/Select";
|
||||
|
||||
export const RendererContainer = observer(() => {
|
||||
const modeManager = useContext(modeManagerContext);
|
||||
const rendererEl = useRef<HTMLDivElement>(null);
|
||||
const { setDomEl, setCallbacks } = useMouseInput();
|
||||
const setRenderer = useGlobalState((state) => state.setRenderer);
|
||||
const onSceneChange = useGlobalState((state) => state.onSceneChange);
|
||||
|
||||
@@ -17,29 +14,10 @@ export const RendererContainer = observer(() => {
|
||||
if (!rendererEl.current) return;
|
||||
|
||||
const renderer = setRenderer(rendererEl.current);
|
||||
setDomEl(rendererEl.current);
|
||||
modeManager.setRenderer(renderer);
|
||||
modeManager.setEventEmitter(renderer.callbacks.emitEvent);
|
||||
modeManager.activateMode(Select);
|
||||
|
||||
setCallbacks({
|
||||
onMouseMove: (event) => {
|
||||
modeManager.onMouseEvent("MOUSE_MOVE", event);
|
||||
},
|
||||
onMouseDown: (event) => {
|
||||
modeManager.onMouseEvent("MOUSE_DOWN", event);
|
||||
},
|
||||
onMouseUp: (event) => {
|
||||
modeManager.onMouseEvent("MOUSE_UP", event);
|
||||
},
|
||||
onMouseEnter: (event) => {
|
||||
modeManager.onMouseEvent("MOUSE_ENTER", event);
|
||||
},
|
||||
onMouseLeave: (event) => {
|
||||
modeManager.onMouseEvent("MOUSE_LEAVE", event);
|
||||
},
|
||||
});
|
||||
}, [setRenderer, setDomEl, modeManager, onSceneChange]);
|
||||
}, [setRenderer, modeManager, onSceneChange]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
|
||||
interface MousePosition {
|
||||
position: Coords;
|
||||
delta: Coords | null;
|
||||
}
|
||||
|
||||
type _MouseEvent = (e: MousePosition) => void;
|
||||
|
||||
interface MouseEvents {
|
||||
onMouseMove: _MouseEvent;
|
||||
onMouseDown: _MouseEvent;
|
||||
onMouseUp: _MouseEvent;
|
||||
onMouseEnter: _MouseEvent;
|
||||
onMouseLeave: _MouseEvent;
|
||||
}
|
||||
|
||||
const getOffset = (domEl?: HTMLElement) => {
|
||||
if (!domEl)
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
const box = domEl.getBoundingClientRect();
|
||||
|
||||
const body = document.body;
|
||||
const docEl = document.documentElement;
|
||||
|
||||
const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
const clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
const top = box.top + scrollTop - clientTop;
|
||||
const left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
};
|
||||
|
||||
export const useMouseInput = () => {
|
||||
const [domEl, setDomEl] = useState<HTMLDivElement>();
|
||||
const [callbacks, setCallbacks] = useState<MouseEvents>();
|
||||
|
||||
const mouseEventToCoords = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
const offset = getOffset(domEl);
|
||||
|
||||
return new Coords(
|
||||
e.clientX - offset.left + window.scrollX,
|
||||
e.clientY - offset.top + window.scrollY
|
||||
);
|
||||
},
|
||||
[domEl]
|
||||
);
|
||||
|
||||
const parseMousePosition = useCallback(
|
||||
(e: MouseEvent, mousedown: Coords | null) => {
|
||||
const current = mouseEventToCoords(e);
|
||||
const delta = mousedown
|
||||
? new Coords(current.x - mousedown.x, current.y - mousedown.y)
|
||||
: null;
|
||||
|
||||
return {
|
||||
position: new Coords(current.x, current.y),
|
||||
delta,
|
||||
};
|
||||
},
|
||||
[mouseEventToCoords]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!callbacks || !domEl) return;
|
||||
|
||||
let lastPosition: Coords | null = null;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
callbacks.onMouseDown(parseMousePosition(e, lastPosition));
|
||||
lastPosition = mouseEventToCoords(e);
|
||||
};
|
||||
|
||||
const onMouseUp = (e: MouseEvent) => {
|
||||
callbacks.onMouseUp(parseMousePosition(e, lastPosition));
|
||||
lastPosition = mouseEventToCoords(e);
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
callbacks.onMouseMove(parseMousePosition(e, lastPosition));
|
||||
lastPosition = mouseEventToCoords(e);
|
||||
};
|
||||
|
||||
const onMouseEnter = (e: MouseEvent) => {
|
||||
callbacks.onMouseEnter(parseMousePosition(e, lastPosition));
|
||||
lastPosition = mouseEventToCoords(e);
|
||||
};
|
||||
|
||||
const onMouseLeave = (e: MouseEvent) => {
|
||||
callbacks.onMouseLeave(parseMousePosition(e, lastPosition));
|
||||
lastPosition = mouseEventToCoords(e);
|
||||
};
|
||||
|
||||
domEl.addEventListener("mousemove", onMouseMove);
|
||||
domEl.addEventListener("mousedown", onMouseDown);
|
||||
domEl.addEventListener("mouseup", onMouseUp);
|
||||
domEl.addEventListener("mouseenter", onMouseEnter);
|
||||
domEl.addEventListener("mouseleave", onMouseLeave);
|
||||
|
||||
return () => {
|
||||
domEl.removeEventListener("mousemove", onMouseMove);
|
||||
domEl.removeEventListener("mousedown", onMouseDown);
|
||||
domEl.removeEventListener("mouseup", onMouseUp);
|
||||
domEl.removeEventListener("mouseenter", onMouseEnter);
|
||||
domEl.removeEventListener("mouseleave", onMouseLeave);
|
||||
};
|
||||
}, [callbacks, domEl]);
|
||||
|
||||
return { setCallbacks, setDomEl };
|
||||
};
|
||||
@@ -1,9 +1,16 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import paper from "paper";
|
||||
import { Renderer } from "../renderer/Renderer";
|
||||
import { Coords } from "../renderer/elements/Coords";
|
||||
import { ModeBase } from "./ModeBase";
|
||||
import type { Mouse, OnSceneChange } from "../types";
|
||||
|
||||
const MOUSE_EVENTS = new Map([
|
||||
["mousemove", "MOUSE_MOVE"],
|
||||
["mousedown", "MOUSE_DOWN"],
|
||||
["mouseup", "MOUSE_UP"],
|
||||
]);
|
||||
|
||||
export class ModeManager {
|
||||
// mobx requires all properties to be initialised explicitly (i.e. prop = undefined)
|
||||
renderer?: Renderer = undefined;
|
||||
@@ -17,13 +24,24 @@ export class ModeManager {
|
||||
delta: null,
|
||||
};
|
||||
emitEvent?: OnSceneChange;
|
||||
tool?: paper.Tool;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
this.onMouseEvent = this.onMouseEvent.bind(this);
|
||||
this.send = this.send.bind(this);
|
||||
}
|
||||
|
||||
setRenderer(renderer: Renderer) {
|
||||
this.renderer = renderer;
|
||||
|
||||
this.tool = new paper.Tool();
|
||||
this.tool.onMouseMove = this.onMouseEvent;
|
||||
this.tool.onMouseDown = this.onMouseEvent;
|
||||
this.tool.onMouseUp = this.onMouseEvent;
|
||||
this.tool.onKeyDown = this.onMouseEvent;
|
||||
this.tool.onKeyUp = this.onMouseEvent;
|
||||
}
|
||||
|
||||
setEventEmitter(fn: OnSceneChange) {
|
||||
@@ -34,6 +52,8 @@ export class ModeManager {
|
||||
Mode: T,
|
||||
init?: (instance: InstanceType<T>) => void
|
||||
) {
|
||||
console.log("ACTIVATING MODE", Mode.name);
|
||||
|
||||
if (!this.renderer) return;
|
||||
|
||||
if (this.currentMode) {
|
||||
@@ -54,10 +74,16 @@ export class ModeManager {
|
||||
this.currentMode.instance.entry(this.mouse);
|
||||
}
|
||||
|
||||
onMouseEvent(eventName: string, mouse: Mouse) {
|
||||
this.mouse = mouse;
|
||||
onMouseEvent(event: paper.ToolEvent) {
|
||||
const type = MOUSE_EVENTS.get(event.type);
|
||||
|
||||
this.send(eventName, mouse);
|
||||
if (!type) return;
|
||||
|
||||
const position = new Coords(event.point.x, event.point.y);
|
||||
const delta = new Coords(event.delta.x, event.delta.y);
|
||||
|
||||
this.mouse = { position, delta };
|
||||
this.send(type, this.mouse);
|
||||
}
|
||||
|
||||
send(eventName: string, params?: any) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export class Pan extends ModeBase {
|
||||
|
||||
MOUSE_MOVE(mouse: Mouse) {
|
||||
if (this.isPanning && mouse.delta !== null) {
|
||||
this.ctx.renderer.scrollToDelta(mouse.delta.x, mouse.delta.y);
|
||||
this.ctx.renderer.scrollToDelta(mouse.delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ export class Select extends ModeBase {
|
||||
|
||||
if (this.dragStartTile && !currentTile.isEqual(this.dragStartTile)) {
|
||||
this.ctx.activateMode(CreateLasso, (mode) => {
|
||||
console.log(this.dragStartTile);
|
||||
this.dragStartTile && mode.setStartTile(this.dragStartTile);
|
||||
mode.MOUSE_MOVE(mouse);
|
||||
});
|
||||
|
||||
@@ -37,17 +37,14 @@ export class Renderer {
|
||||
container: HTMLDivElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
};
|
||||
scrollPosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
scrollPosition = new Coords(0, 0);
|
||||
rafRef?: number;
|
||||
|
||||
constructor(containerEl: HTMLDivElement) {
|
||||
makeAutoObservable(this);
|
||||
|
||||
Paper.settings = {
|
||||
insertelements: false,
|
||||
insertItems: false,
|
||||
applyMatrix: false,
|
||||
};
|
||||
|
||||
@@ -57,7 +54,7 @@ export class Renderer {
|
||||
|
||||
this.domElements = {
|
||||
container: containerEl,
|
||||
...this.initDOM(containerEl),
|
||||
canvas: this.initDOM(containerEl).canvas,
|
||||
};
|
||||
|
||||
Paper.setup(this.domElements.canvas);
|
||||
@@ -83,7 +80,7 @@ export class Renderer {
|
||||
this.activeLayer = Paper.project.activeLayer;
|
||||
this.activeLayer.addChild(this.groups.container);
|
||||
|
||||
this.scrollTo(0, 0);
|
||||
this.scrollTo(new Coords(0, 0));
|
||||
|
||||
this.render();
|
||||
|
||||
@@ -102,6 +99,8 @@ export class Renderer {
|
||||
scene.nodes.forEach((node) => {
|
||||
this.sceneElements.nodes.addNode(node);
|
||||
});
|
||||
|
||||
this.setZoom(1);
|
||||
}
|
||||
|
||||
getIconById(id: string) {
|
||||
@@ -131,13 +130,17 @@ export class Renderer {
|
||||
const halfW = PROJECTED_TILE_WIDTH / 2;
|
||||
const halfH = PROJECTED_TILE_HEIGHT / 2;
|
||||
|
||||
const mouseX =
|
||||
(mouse.x - this.groups.elements.position.x) * (1 / this.zoom);
|
||||
const mouseY =
|
||||
(mouse.y - this.groups.elements.position.y) * (1 / this.zoom) + halfH;
|
||||
const canvasPosition = new Coords(
|
||||
mouse.x - this.groups.elements.position.x,
|
||||
mouse.y - this.groups.elements.position.y + halfH
|
||||
);
|
||||
|
||||
const row = Math.floor((mouseX / halfW + mouseY / halfH) / 2);
|
||||
const col = Math.floor((mouseY / halfH - mouseX / halfW) / 2);
|
||||
const row = Math.floor(
|
||||
(canvasPosition.x / halfW + canvasPosition.y / halfH) / 2
|
||||
);
|
||||
const col = Math.floor(
|
||||
(canvasPosition.y / halfH - canvasPosition.x / halfW) / 2
|
||||
);
|
||||
|
||||
const halfRowNum = Math.floor(this.sceneElements.grid.size.x * 0.5);
|
||||
const halfColNum = Math.floor(this.sceneElements.grid.size.y * 0.5);
|
||||
@@ -208,6 +211,9 @@ export class Renderer {
|
||||
gsap.to(Paper.view, {
|
||||
duration: 0.3,
|
||||
zoom: this.zoom,
|
||||
onComplete: () => {
|
||||
this.scrollTo(this.scrollPosition);
|
||||
},
|
||||
});
|
||||
|
||||
this.emitEvent({
|
||||
@@ -216,15 +222,15 @@ export class Renderer {
|
||||
});
|
||||
}
|
||||
|
||||
scrollTo(x: number, y: number) {
|
||||
this.scrollPosition = { x, y };
|
||||
scrollTo(coords: Coords) {
|
||||
this.scrollPosition.set(coords.x, coords.y);
|
||||
|
||||
const { center: viewCenter } = Paper.view.bounds;
|
||||
|
||||
const newPosition = {
|
||||
x: x + viewCenter.x,
|
||||
y: y + viewCenter.y,
|
||||
};
|
||||
const newPosition = new Coords(
|
||||
coords.x + viewCenter.x,
|
||||
coords.y + viewCenter.y
|
||||
);
|
||||
|
||||
gsap.to(this.groups.elements.position, {
|
||||
duration: 0,
|
||||
@@ -232,11 +238,10 @@ export class Renderer {
|
||||
});
|
||||
}
|
||||
|
||||
scrollToDelta(deltaX: number, deltaY: number) {
|
||||
this.scrollTo(
|
||||
this.scrollPosition.x + deltaX * (1 / this.zoom),
|
||||
this.scrollPosition.y + deltaY * (1 / this.zoom)
|
||||
);
|
||||
scrollToDelta(delta: Coords) {
|
||||
const position = this.scrollPosition.add(delta);
|
||||
|
||||
this.scrollTo(position);
|
||||
}
|
||||
|
||||
unfocusAll() {
|
||||
|
||||
Reference in New Issue
Block a user