From 51bdd4eff8d227cea55aa21e7ffe3a09e11c221d Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Wed, 16 Apr 2025 15:01:44 -0700 Subject: [PATCH] Create a really sick precision selector (#506) Steps to activate: 1 - Select an object 2 - In the transform box, click and hold "Scale" 3 - Drag vertically to select the incremental precision 4 - Drag horizontally to apply increments of the selected precision Issues: - Releasing the mouse on top of the canvas will also trigger a "mouseup" event on the canvas (since it listens to the window, not the canvas) and deselect the object. --- .../app/pages/PageEnigma/PageEnigma.tsx | 10 +- .../ControlPanelSceneObject.tsx | 14 ++- .../DraggablePrecisionMutator.tsx | 101 ++++++++++++++++++ .../PrecisionSelector/PrecisionSelector.tsx | 43 ++++++++ .../signals/precisionSelectorMenu.ts | 6 ++ 5 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 frontend/apps/editor3d/app/pages/PageEnigma/comps/ControlPanelSceneObject/DraggablePrecisionMutator.tsx create mode 100644 frontend/apps/editor3d/app/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx create mode 100644 frontend/apps/editor3d/app/pages/PageEnigma/signals/precisionSelectorMenu.ts diff --git a/frontend/apps/editor3d/app/pages/PageEnigma/PageEnigma.tsx b/frontend/apps/editor3d/app/pages/PageEnigma/PageEnigma.tsx index d0b9fe5694..f1ce26316e 100644 --- a/frontend/apps/editor3d/app/pages/PageEnigma/PageEnigma.tsx +++ b/frontend/apps/editor3d/app/pages/PageEnigma/PageEnigma.tsx @@ -16,6 +16,8 @@ import * as gpu from "detect-gpu"; import { UsersApi } from "~/Classes/ApiManager"; import { PageEnigmaComponent } from "~/pages/PageEnigma/PageEnigmaComponent"; import { TurnOnGpu } from "~/pages/PageEnigma/TurnOnGpu"; +import { PrecisionSelector } from "./comps/PrecisionSelector/PrecisionSelector"; +import { precisionSelectedValue, precisionSelectorMenuCoords, precisionSelectorValues, showPrecisionSelector } from "./signals/precisionSelectorMenu"; export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { useSignals(); @@ -71,7 +73,7 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { case "apple gpu (Apple GPU)": // TODO(bt,2025-04-08): We may want to disable this heuristic. // We're getting lack of hardware acceleration using Tauri on Mac and Linux. - isValid = true; + isValid = true; break; default: break; @@ -93,6 +95,12 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => { + diff --git a/frontend/apps/editor3d/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/frontend/apps/editor3d/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index 346f4b73ec..2a9fc613a7 100644 --- a/frontend/apps/editor3d/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/frontend/apps/editor3d/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -22,6 +22,7 @@ import { sanitize } from "./utils/sanitize"; import { objectMismatch } from "~/pages/PageEnigma/comps/ControlPanelSceneObject/utils/objectMismatch"; import { XYZ } from "~/pages/PageEnigma/datastructures/common"; import { pageHeight } from "~/signals"; +import { DraggablePrecisionMutator } from "./DraggablePrecisionMutator"; // TODO this will be useful later to fix the bug on leading zeros // const formatNumber = (input: string): number => { @@ -147,6 +148,15 @@ export const ControlPanelSceneObject = () => { setLocalRotation(xyz); }; + const handleUniformScaleChange = (scale: number) => { + const updatedScaleValues: Record = {}; + updatedScaleValues.x = (parseFloat(localScale.x) + scale).toString(); + updatedScaleValues.y = (parseFloat(localScale.y) + scale).toString(); + updatedScaleValues.z = (parseFloat(localScale.z) + scale).toString(); + + handleScaleChange(updatedScaleValues); + } + const handleScaleChange = (xyz: Record) => { if (isInvalid(xyz)) { setLocalScale(xyz); @@ -255,7 +265,9 @@ export const ControlPanelSceneObject = () => {
-
Scale
+ +
Scale
+
{ }, + onCommit = () => { }, + children +}: { + precisions?: number[], + className?: string, + initialValue?: number, + onChange?: (value: number) => void, + onCommit?: (value: number) => void, + children?: React.ReactNode +}) => { + const classes = twMerge(["cursor-ew-resize", className]); + const divRef = useRef(null); + + const isDraggingRef = useRef(false); + const dragStartCoordsRef = useRef<{ x: number, y: number } | null>(null); + const localAdjustedValueRef = useRef(initialValue); + + const handleMouseMove = useCallback((event: MouseEvent) => { + // Ignore mouse movement if we're not even dragging + // or if the menu is still open (we're selecting the precision) + if (!isDraggingRef.current || showPrecisionSelector.value) { + return; + } + + event.stopPropagation(); + + // Record the initial drag coordinates if they haven't been set yet + if (!dragStartCoordsRef.current) { + dragStartCoordsRef.current = { + x: event.clientX, + y: event.clientY + }; + return; + } + + // Check how far the mouse has moved, and calculate the updated change + const distX = event.x - dragStartCoordsRef.current.x; + const scaledDistX = distX / 10; + const scaledPrecision = Math.round(scaledDistX) * precisionSelectedValue.value; + localAdjustedValueRef.current = initialValue + scaledPrecision; + + // Update the listeners + onChange(localAdjustedValueRef.current); + }, [initialValue, onChange]); + + const handleMouseUp = useCallback((event: MouseEvent) => { + event.stopPropagation(); + showPrecisionSelector.value = false; + isDraggingRef.current = false; + dragStartCoordsRef.current = null; + + // clean up the mouse listeners + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + + // Call back the commit listener + onCommit(localAdjustedValueRef.current); + }, [handleMouseMove, onCommit]); + + const handleMouseDown = useCallback((event: React.MouseEvent) => { + if (event.button !== 0) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + precisionSelectorMenuCoords.value = { + x: event.clientX, + y: event.clientY + }; + precisionSelectorValues.value = precisions; + showPrecisionSelector.value = true; + isDraggingRef.current = true; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }, [handleMouseMove, handleMouseUp, precisions]); + + return ( +
+ {children} +
+ ) +} diff --git a/frontend/apps/editor3d/app/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx b/frontend/apps/editor3d/app/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx new file mode 100644 index 0000000000..57ad7dcbb9 --- /dev/null +++ b/frontend/apps/editor3d/app/pages/PageEnigma/comps/PrecisionSelector/PrecisionSelector.tsx @@ -0,0 +1,43 @@ +import { Signal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; + +export const PrecisionSelector = ({ + showSignal, + coordSignal, + valuesSignal, + selectedValueSignal +}: { + showSignal: Signal, + coordSignal: Signal<{ x: number; y: number }>, + valuesSignal: Signal + selectedValueSignal: Signal +}) => { + useSignals(); + + const handleMouseLeave = () => { + showSignal.value = false; + } + + const handleMouseEnterItem = (scale: number) => { + selectedValueSignal.value = scale; + console.log("selectedValueSignal", selectedValueSignal.value); + } + + return ( +
+ {valuesSignal.value.map((scale, index) => ( + handleMouseEnterItem(scale)} key={index} + className="flex bg-ui-panel text-sm justify-center align-middle justify-items-center px-2 hover:bg-ui-controls" + >{scale} + ))} +
+ ) +} diff --git a/frontend/apps/editor3d/app/pages/PageEnigma/signals/precisionSelectorMenu.ts b/frontend/apps/editor3d/app/pages/PageEnigma/signals/precisionSelectorMenu.ts new file mode 100644 index 0000000000..cb5c1504b5 --- /dev/null +++ b/frontend/apps/editor3d/app/pages/PageEnigma/signals/precisionSelectorMenu.ts @@ -0,0 +1,6 @@ +import { Signal, signal } from "@preact/signals-react"; + +export const showPrecisionSelector = signal(false); +export const precisionSelectorMenuCoords = signal({ x: 0, y: 0 }); +export const precisionSelectorValues: Signal = signal([]); +export const precisionSelectedValue = signal(0);