fix: rewrite coordinate management

After some initial smaller fixes, it turned out that I had broken
the red line used when linking fields. Fixing this was not trivial
as I found myself battling a lot of small bugs relating to scale
and translation in the existing code. This was made extra difficult
as a lot of coordinates were calculated when necessary in
Canvas.jsx.

This commit attempts to simplify the coordinate management in a few
different ways:
* There are now two distinct coordinate systems in use, typically
  referred to as "spaces". Screen space and diagram space.
* Diagram space is no longer measured in pixels (though the
  dimension-less measure used instead still maps to pixels at 100%
  zoom).
* The canvas now exposes helper methods for transforming between
  spaces.
* Zoom and translation is now managed via the svg viewBox property.
  * This makes moving items in diagram space much easier as the
    coordinates remain constant regardless of zoom level.
* The canvas now wraps the current mouse position in a context
  object, making mouse movement much easier to work with.
* The transform.pan property now refers to the center of the screen.

A new feature in this commit is that scroll wheel zoom is now based
on the current cursor location, making the diagram more convenient
to move around in.

I have tried to focus on Canvas.jsx and avoid changes that might be
desctructive on existing save files. I also believe more refactors
and abstractions could be introduced based on these changes to make
the diagram even easier to work with. However, I deem that out of
scope for now.
This commit is contained in:
Felix Zedén Yverås
2024-07-01 00:53:53 +02:00
parent 32c82168fe
commit e4e22dee20
11 changed files with 437 additions and 203 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useContext, useRef, useState } from "react";
import { Button, Popover, Input } from "@douyinfe/semi-ui";
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
import {
@@ -15,16 +15,27 @@ import {
useSelect,
useAreas,
useSaveState,
useTransform,
} from "../../hooks";
import ColorPalette from "../ColorPicker";
import { useTranslation } from "react-i18next";
import { useHover } from "usehooks-ts";
import { CanvasContext } from "../../context/CanvasContext";
export default function Area({ data, onPointerDown, setResize, setInitCoords }) {
const [hovered, setHovered] = useState(false);
export default function Area({
data,
onPointerDown,
setResize,
setInitCoords,
}) {
const ref = useRef(null);
const isHovered = useHover(ref);
const {
pointer: {
spaces: { diagram: pointer },
},
} = useContext(CanvasContext);
const { layout } = useLayout();
const { settings } = useSettings();
const { transform } = useTransform();
const { setSaveState } = useSaveState();
const { selectedElement, setSelectedElement } = useSelect();
@@ -35,8 +46,8 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
y: data.y,
width: data.width,
height: data.height,
pointerX: e.clientX / transform.zoom,
pointerY: e.clientY / transform.zoom,
pointerX: pointer.x,
pointerY: pointer.y,
});
};
@@ -84,10 +95,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
selectedElement.open;
return (
<g
onPointerEnter={(e) => e.isPrimary && setHovered(true)}
onPointerLeave={(e) => e.isPrimary &&setHovered(false)}
>
<g ref={ref}>
<foreignObject
key={data.id}
x={data.x}
@@ -98,7 +106,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
>
<div
className={`border-2 ${
hovered
isHovered
? "border-dashed border-blue-500"
: selectedElement.element === ObjectType.AREA &&
selectedElement.id === data.id
@@ -114,7 +122,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
<div className="text-color select-none overflow-hidden text-ellipsis">
{data.name}
</div>
{(hovered || (areaIsSelected() && !layout.sidebar)) && (
{(isHovered || (areaIsSelected() && !layout.sidebar)) && (
<Popover
visible={areaIsSelected() && !layout.sidebar}
onClickOutSide={onClickOutSide}
@@ -139,7 +147,7 @@ export default function Area({ data, onPointerDown, setResize, setInitCoords })
</div>
</div>
</foreignObject>
{hovered && (
{isHovered && (
<>
<circle
cx={data.x}