From cdecf7c633d97649f63c0cbbc9546e3b2068b2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Zed=C3=A9n=20Yver=C3=A5s?= Date: Wed, 26 Jun 2024 20:43:56 +0200 Subject: [PATCH] feat: add basic touchscreen support This is basically a migration from mouse events to [pointer events]( https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events ). The `PointerEvent` interface inherits all of the `MouseEvent` properties, meaning that existing code can essentially be left as-is. The only major change is making sure we only respond to the "primary" pointer. Known issues include: * stylus hover is not detected * touchscreens do not have a concept of hover, making it difficult to e.g. resize areas * no touch gesture support, e.g. "pinch-to-zoom" --- src/components/EditorCanvas/Area.jsx | 20 ++--- src/components/EditorCanvas/Canvas.jsx | 90 ++++++++++++-------- src/components/EditorCanvas/Note.jsx | 8 +- src/components/EditorCanvas/Table.jsx | 16 ++-- src/components/EditorHeader/ControlPanel.jsx | 4 +- src/components/EditorSidePanel/SidePanel.jsx | 2 +- src/components/SimpleCanvas.jsx | 16 ++-- src/components/Workspace.jsx | 6 +- 8 files changed, 95 insertions(+), 67 deletions(-) diff --git a/src/components/EditorCanvas/Area.jsx b/src/components/EditorCanvas/Area.jsx index 5480217..7ebfe5a 100644 --- a/src/components/EditorCanvas/Area.jsx +++ b/src/components/EditorCanvas/Area.jsx @@ -20,7 +20,7 @@ import { import ColorPalette from "../ColorPicker"; import { useTranslation } from "react-i18next"; -export default function Area({ data, onMouseDown, setResize, setInitCoords }) { +export default function Area({ data, onPointerDown, setResize, setInitCoords }) { const [hovered, setHovered] = useState(false); const { layout } = useLayout(); const { settings } = useSettings(); @@ -35,8 +35,8 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) { y: data.y, width: data.width, height: data.height, - mouseX: e.clientX / transform.zoom, - mouseY: e.clientY / transform.zoom, + pointerX: e.clientX / transform.zoom, + pointerY: e.clientY / transform.zoom, }); }; @@ -85,8 +85,8 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) { return ( setHovered(true)} - onMouseLeave={() => setHovered(false)} + onPointerEnter={(e) => e.isPrimary && setHovered(true)} + onPointerLeave={(e) => e.isPrimary &&setHovered(false)} > 0 ? data.width : 0} height={data.height > 0 ? data.height : 0} - onMouseDown={onMouseDown} + onPointerDown={onPointerDown} >
handleResize(e, "tl")} + onPointerDown={(e) => e.isPrimary && handleResize(e, "tl")} /> handleResize(e, "tr")} + onPointerDown={(e) => e.isPrimary && handleResize(e, "tr")} /> handleResize(e, "bl")} + onPointerDown={(e) => e.isPrimary && handleResize(e, "bl")} /> handleResize(e, "br")} + onPointerDown={(e) => e.isPrimary && handleResize(e, "br")} /> )} diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx index 0dab1b9..7671eec 100644 --- a/src/components/EditorCanvas/Canvas.jsx +++ b/src/components/EditorCanvas/Canvas.jsx @@ -68,14 +68,21 @@ export default function Canvas() { y: 0, width: 0, height: 0, - mouseX: 0, - mouseY: 0, + pointerX: 0, + pointerY: 0, }); const [cursor, setCursor] = useState("default"); const canvas = useRef(null); - const handleMouseDownOnElement = (e, id, type) => { + /** + * @param {PointerEvent} e + * @param {*} id + * @param {ObjectType[keyof ObjectType]} type + */ + const handlePointerDownOnElement = (e, id, type) => { + if (!e.isPrimary) return; + const { clientX, clientY } = e; if (type === ObjectType.TABLE) { const table = tables.find((t) => t.id === id); @@ -122,7 +129,12 @@ export default function Canvas() { })); }; - const handleMouseMove = (e) => { + /** + * @param {PointerEvent} e + */ + const handlePointerMove = (e) => { + if (!e.isPrimary) return; + if (linking) { const rect = canvas.current.getBoundingClientRect(); setLinkingLine({ @@ -164,34 +176,39 @@ export default function Canvas() { } else if (areaResize.id !== -1) { if (areaResize.dir === "none") return; let newDims = { ...initCoords }; - delete newDims.mouseX; - delete newDims.mouseY; - const mouseX = e.clientX / transform.zoom; - const mouseY = e.clientY / transform.zoom; + delete newDims.pointerX; + delete newDims.pointerY; + const pointerX = e.clientX / transform.zoom; + const pointerY = e.clientY / transform.zoom; setPanning({ isPanning: false, x: 0, y: 0 }); if (areaResize.dir === "br") { - newDims.width = initCoords.width + (mouseX - initCoords.mouseX); - newDims.height = initCoords.height + (mouseY - initCoords.mouseY); + newDims.width = initCoords.width + (pointerX - initCoords.pointerX); + newDims.height = initCoords.height + (pointerY - initCoords.pointerY); } else if (areaResize.dir === "tl") { - newDims.x = initCoords.x + (mouseX - initCoords.mouseX); - newDims.y = initCoords.y + (mouseY - initCoords.mouseY); - newDims.width = initCoords.width - (mouseX - initCoords.mouseX); - newDims.height = initCoords.height - (mouseY - initCoords.mouseY); + newDims.x = initCoords.x + (pointerX - initCoords.pointerX); + newDims.y = initCoords.y + (pointerY - initCoords.pointerY); + newDims.width = initCoords.width - (pointerX - initCoords.pointerX); + newDims.height = initCoords.height - (pointerY - initCoords.pointerY); } else if (areaResize.dir === "tr") { - newDims.y = initCoords.y + (mouseY - initCoords.mouseY); - newDims.width = initCoords.width + (mouseX - initCoords.mouseX); - newDims.height = initCoords.height - (mouseY - initCoords.mouseY); + newDims.y = initCoords.y + (pointerY - initCoords.pointerY); + newDims.width = initCoords.width + (pointerX - initCoords.pointerX); + newDims.height = initCoords.height - (pointerY - initCoords.pointerY); } else if (areaResize.dir === "bl") { - newDims.x = initCoords.x + (mouseX - initCoords.mouseX); - newDims.width = initCoords.width - (mouseX - initCoords.mouseX); - newDims.height = initCoords.height + (mouseY - initCoords.mouseY); + newDims.x = initCoords.x + (pointerX - initCoords.pointerX); + newDims.width = initCoords.width - (pointerX - initCoords.pointerX); + newDims.height = initCoords.height + (pointerY - initCoords.pointerY); } updateArea(areaResize.id, { ...newDims }); } }; - const handleMouseDown = (e) => { + /** + * @param {PointerEvent} e + */ + const handlePointerDown = (e) => { + if (!e.isPrimary) return; + // don't pan if the sidesheet for editing a table is open if ( selectedElement.element === ObjectType.TABLE && @@ -268,7 +285,12 @@ export default function Canvas() { } }; - const handleMouseUp = () => { + /** + * @param {PointerEvent} e + */ + const handlePointerUp = (e) => { + if (!e.isPrimary) return; + if (coordsDidUpdate(dragging.element)) { const info = getMovedElementDetails(); setUndoStack((prev) => [ @@ -344,8 +366,8 @@ export default function Canvas() { y: 0, width: 0, height: 0, - mouseX: 0, - mouseY: 0, + pointerX: 0, + pointerY: 0, }); }; @@ -411,12 +433,12 @@ export default function Canvas() { const theme = localStorage.getItem("theme"); return ( -
+
- handleMouseDownOnElement(e, a.id, ObjectType.AREA) + onPointerDown={(e) => + handlePointerDownOnElement(e, a.id, ObjectType.AREA) } setResize={setAreaResize} setInitCoords={setInitCoords} @@ -481,8 +503,8 @@ export default function Canvas() { setHoveredTable={setHoveredTable} handleGripField={handleGripField} setLinkingLine={setLinkingLine} - onMouseDown={(e) => - handleMouseDownOnElement(e, table.id, ObjectType.TABLE) + onPointerDown={(e) => + handlePointerDownOnElement(e, table.id, ObjectType.TABLE) } /> ))} @@ -497,8 +519,8 @@ export default function Canvas() { - handleMouseDownOnElement(e, n.id, ObjectType.NOTE) + onPointerDown={(e) => + handlePointerDownOnElement(e, n.id, ObjectType.NOTE) } /> ))} diff --git a/src/components/EditorCanvas/Note.jsx b/src/components/EditorCanvas/Note.jsx index e2d67fc..381682e 100644 --- a/src/components/EditorCanvas/Note.jsx +++ b/src/components/EditorCanvas/Note.jsx @@ -21,7 +21,7 @@ import { } from "../../hooks"; import { useTranslation } from "react-i18next"; -export default function Note({ data, onMouseDown }) { +export default function Note({ data, onPointerDown }) { const w = 180; const r = 3; const fold = 24; @@ -83,8 +83,8 @@ export default function Note({ data, onMouseDown }) { return ( setHovered(true)} - onMouseLeave={() => setHovered(false)} + onPointerEnter={(e) => e.isPrimary && setHovered(true)} + onPointerLeave={(e) => e.isPrimary && setHovered(false)} >
diff --git a/src/components/EditorCanvas/Table.jsx b/src/components/EditorCanvas/Table.jsx index e0b753a..3dfd84f 100644 --- a/src/components/EditorCanvas/Table.jsx +++ b/src/components/EditorCanvas/Table.jsx @@ -22,7 +22,7 @@ export default function Table(props) { const [hoveredField, setHoveredField] = useState(-1); const { tableData, - onMouseDown, + onPointerDown, setHoveredTable, handleGripField, setLinkingLine, @@ -67,7 +67,7 @@ export default function Table(props) { width={settings.tableWidth} height={height} className="group drop-shadow-lg rounded-md cursor-move" - onMouseDown={onMouseDown} + onPointerDown={onPointerDown} >
{ + onPointerEnter={(e) => { + if (!e.isPrimary) return; + setHoveredField(index); setHoveredTable({ tableId: tableData.id, field: index, }); }} - onMouseLeave={() => { + onPointerLeave={(e) => { + if (!e.isPrimary) return; + setHoveredField(-1); }} > @@ -284,7 +288,9 @@ export default function Table(props) { >