diff --git a/backend/dist/nsfwapp-linux-amd64 b/backend/dist/nsfwapp-linux-amd64 index 985c5db..c226164 100644 Binary files a/backend/dist/nsfwapp-linux-amd64 and b/backend/dist/nsfwapp-linux-amd64 differ diff --git a/backend/dist/nsfwapp.exe b/backend/dist/nsfwapp.exe index ae20ec1..45ae712 100644 Binary files a/backend/dist/nsfwapp.exe and b/backend/dist/nsfwapp.exe differ diff --git a/backend/dist/nsfwapp_amd64.deb b/backend/dist/nsfwapp_amd64.deb index d438d55..fed376a 100644 Binary files a/backend/dist/nsfwapp_amd64.deb and b/backend/dist/nsfwapp_amd64.deb differ diff --git a/frontend/src/components/ui/TrainingTab.tsx b/frontend/src/components/ui/TrainingTab.tsx index 9bb94ed..276d3d2 100644 --- a/frontend/src/components/ui/TrainingTab.tsx +++ b/frontend/src/components/ui/TrainingTab.tsx @@ -218,6 +218,9 @@ type PoseInteraction = { type: 'keypoint' personIndex: number keypointIndex: number + startClientX: number + startClientY: number + moved: boolean } type PoseKeypointPlacement = { @@ -225,6 +228,11 @@ type PoseKeypointPlacement = { keypointName: string } +type PoseKeypointAction = { + personIndex: number + keypointIndex: number +} + type MagnifierState = { visible: boolean clientX: number @@ -366,6 +374,7 @@ const POSE_RELIABLE_KEYPOINT_MIN_CONFIDENCE = 0.20 const POSE_RELIABLE_MIN_SCORE = 0.30 const POSE_RELIABLE_MIN_KEYPOINTS = 6 const POSE_RELIABLE_MIN_QUALITY = 0.45 +const POSE_KEYPOINT_TAP_MOVE_THRESHOLD_PX = 6 const POSE_UNRELIABLE_COLOR = '#94a3b8' const POSE_PERSON_COLORS = ['#38bdf8', '#a78bfa', '#34d399', '#f59e0b', '#fb7185'] const POSE_SKELETON_EDGES: Array = [ @@ -4241,6 +4250,7 @@ export default function TrainingTab(props: { setActivePosePersonIndex(null) setPendingPoseKeypoint(null) setHoveredPoseKeypoint(null) + setSelectedPoseKeypointAction(null) if (poseKeypointMapAnimationFrameRef.current !== null) { cancelAnimationFrame(poseKeypointMapAnimationFrameRef.current) poseKeypointMapAnimationFrameRef.current = null @@ -4401,6 +4411,8 @@ export default function TrainingTab(props: { const [poseInteraction, setPoseInteraction] = useState(null) const [pendingPoseKeypoint, setPendingPoseKeypoint] = useState(null) const [hoveredPoseKeypoint, setHoveredPoseKeypoint] = useState(null) + const [selectedPoseKeypointAction, setSelectedPoseKeypointAction] = + useState(null) const [poseKeypointMapView, setPoseKeypointMapView] = useState('body') const [poseKeypointMapViewBoxValue, setPoseKeypointMapViewBoxValue] = useState(POSE_BODY_MAP_VIEW_BOX) @@ -4560,6 +4572,7 @@ export default function TrainingTab(props: { setActivePosePersonIndex(null) setPendingPoseKeypoint(null) setHoveredPoseKeypoint(null) + setSelectedPoseKeypointAction(null) poseFaceImagePanGestureRef.current = null poseFaceImagePanCenterRef.current = null @@ -7369,14 +7382,38 @@ export default function TrainingTab(props: { : clampedPos if (poseInteraction) { + const pointerDistance = Math.hypot( + snapshot.clientX - poseInteraction.startClientX, + snapshot.clientY - poseInteraction.startClientY + ) + const hasMoved = + poseInteraction.moved || + pointerDistance > POSE_KEYPOINT_TAP_MOVE_THRESHOLD_PX + + if (!hasMoved) { + return + } + + if (!poseInteraction.moved) { + const nextInteraction = { + ...poseInteraction, + moved: true, + } + + poseInteractionRef.current = nextInteraction + setPoseInteraction(nextInteraction) + setSelectedPoseKeypointAction(null) + } + markManualCorrection() let nextPoseMagnifier: MagnifierState | null = null + const activePoseInteraction = poseInteractionRef.current ?? poseInteraction setCorrection((prev) => { const persons = clonePosePersons(prev.posePersons) - const person = persons[poseInteraction.personIndex] - const point = person?.keypoints?.[poseInteraction.keypointIndex] + const person = persons[activePoseInteraction.personIndex] + const point = person?.keypoints?.[activePoseInteraction.keypointIndex] if (!person || !point) { return prev @@ -7402,15 +7439,15 @@ export default function TrainingTab(props: { box: poseBoxFromKeypoints({ ...person, keypoints: person.keypoints.map((candidate, index) => - index === poseInteraction.keypointIndex ? nextPoint : candidate + index === activePoseInteraction.keypointIndex ? nextPoint : candidate ), }), keypoints: person.keypoints.map((candidate, index) => - index === poseInteraction.keypointIndex ? nextPoint : candidate + index === activePoseInteraction.keypointIndex ? nextPoint : candidate ), }) - persons[poseInteraction.personIndex] = nextPerson + persons[activePoseInteraction.personIndex] = nextPerson const next: CorrectionState = { ...prev, @@ -7692,6 +7729,7 @@ export default function TrainingTab(props: { y: clamp01(rawPos.y), } ) + setSelectedPoseKeypointAction(null) return } @@ -7822,6 +7860,17 @@ export default function TrainingTab(props: { } if (activePoseInteraction) { + if (activePoseInteraction.moved) { + setSelectedPoseKeypointAction(null) + } else { + setSelectedPoseKeypointAction({ + personIndex: activePoseInteraction.personIndex, + keypointIndex: activePoseInteraction.keypointIndex, + }) + setPendingPoseKeypoint(null) + setActivePosePersonIndex(activePoseInteraction.personIndex) + } + setPoseInteraction(null) clearBoxGestureRefs() return @@ -8007,6 +8056,18 @@ export default function TrainingTab(props: { } return current }) + + setSelectedPoseKeypointAction((current) => { + if (!current) return null + if (current.personIndex === personIndex) return null + if (current.personIndex > personIndex) { + return { + ...current, + personIndex: current.personIndex - 1, + } + } + return current + }) }, [markManualCorrection]) const addPosePerson = useCallback(() => { @@ -8031,7 +8092,16 @@ export default function TrainingTab(props: { setActiveBoxIndex(null) setActivePosePersonIndex(nextPersonIndex) setPendingPoseKeypoint(null) + setSelectedPoseKeypointAction(null) switchPoseKeypointMapView('body') + setMobilePanel('labels') + + window.requestAnimationFrame(() => { + mobileLabelsScrollRef.current?.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }) + }) }, [markManualCorrection, switchPoseKeypointMapView, uiLocked]) const removePoseKeypoint = useCallback((personIndex: number, keypointIndex: number) => { @@ -8074,6 +8144,12 @@ export default function TrainingTab(props: { correctionRef.current = next return next }) + + setSelectedPoseKeypointAction((current) => + current?.personIndex === personIndex && current.keypointIndex === keypointIndex + ? null + : current + ) }, [markManualCorrection]) const resetPosePersons = useCallback(() => { @@ -8091,6 +8167,7 @@ export default function TrainingTab(props: { }) setActivePosePersonIndex(null) + setSelectedPoseKeypointAction(null) if (poseKeypointMapAnimationFrameRef.current !== null) { cancelAnimationFrame(poseKeypointMapAnimationFrameRef.current) poseKeypointMapAnimationFrameRef.current = null @@ -8162,11 +8239,15 @@ export default function TrainingTab(props: { type: 'keypoint', personIndex, keypointIndex, + startClientX: e.clientX, + startClientY: e.clientY, + moved: false, } poseInteractionRef.current = nextInteraction setPoseInteraction(nextInteraction) setActivePosePersonIndex(personIndex) + setSelectedPoseKeypointAction(null) setTouchMagnifier({ visible: true, clientX: e.clientX, @@ -9803,6 +9884,7 @@ export default function TrainingTab(props: { if (uiLocked) return setShowPoseSkeleton(true) setActivePosePersonIndex(personIndex) + setSelectedPoseKeypointAction(null) setPendingPoseKeypoint({ personIndex, keypointName: poseKeypointId(point.name), @@ -10117,11 +10199,13 @@ export default function TrainingTab(props: { if (pending) { setPendingPoseKeypoint(null) + setSelectedPoseKeypointAction(null) return } setShowPoseSkeleton(true) setActivePosePersonIndex(targetIndex) + setSelectedPoseKeypointAction(null) setPendingPoseKeypoint({ personIndex: targetIndex, keypointName, @@ -10877,14 +10961,18 @@ export default function TrainingTab(props: { const selected = pendingPoseKeypoint?.personIndex === personIndex && pendingPoseKeypoint.keypointName === keypointId + const actionSelected = + selectedPoseKeypointAction?.personIndex === personIndex && + selectedPoseKeypointAction.keypointIndex === pointIndex const hovered = hoveredPoseKeypoint?.personIndex === personIndex && hoveredPoseKeypoint.keypointName === keypointId const dragging = poseInteraction?.personIndex === personIndex && poseInteraction.keypointIndex === pointIndex - const highlighted = selected || hovered || dragging - const showLabel = selected || dragging + const highlighted = selected || actionSelected || hovered || dragging + const showLabel = selected || actionSelected || dragging + const showDeleteAction = actionSelected && !dragging && !uiLocked const facePlacement = poseFaceImageZoomActive ? poseFaceKeypointLabelPlacement(keypointId) : null @@ -10939,7 +11027,11 @@ export default function TrainingTab(props: { {showLabel ? (
{label} + + {showDeleteAction ? ( + + ) : null}
) : null}