-
Notifications
You must be signed in to change notification settings - Fork 43
feat: add smooth Bezier movement to dragMouse #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f508bfa
86e3a10
1fcf61b
f12f833
3884879
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,113 @@ func (t *HumanizeMouseTrajectory) GetPointsInt() [][2]int { | |
| return out | ||
| } | ||
|
|
||
| // MultiSegmentResult holds the generated trajectory and the per-step delay. | ||
| type MultiSegmentResult struct { | ||
| Points [][2]int | ||
| StepDelayMs int | ||
| } | ||
|
|
||
| const defaultStepDelayMs = 10 | ||
|
|
||
| // GenerateMultiSegmentTrajectory creates a human-like Bezier trajectory through | ||
| // a sequence of waypoints. Each consecutive pair gets its own Bezier curve, with | ||
| // point counts distributed proportionally to segment distance. The resulting | ||
| // points are clamped to [0, screenW-1] x [0, screenH-1]. | ||
| func GenerateMultiSegmentTrajectory(waypoints [][2]int, screenW, screenH int, totalDurationMs *int) MultiSegmentResult { | ||
| if len(waypoints) < 2 { | ||
| return MultiSegmentResult{Points: waypoints, StepDelayMs: defaultStepDelayMs} | ||
| } | ||
|
|
||
| segDistances := make([]float64, len(waypoints)-1) | ||
| var totalDist float64 | ||
| for i := 1; i < len(waypoints); i++ { | ||
| dx := float64(waypoints[i][0] - waypoints[i-1][0]) | ||
| dy := float64(waypoints[i][1] - waypoints[i-1][1]) | ||
| d := math.Sqrt(dx*dx + dy*dy) | ||
| segDistances[i-1] = d | ||
| totalDist += d | ||
| } | ||
|
|
||
| // Determine total number of points across all segments. | ||
| var totalPoints int | ||
| if totalDurationMs != nil && *totalDurationMs > 0 { | ||
| totalPoints = *totalDurationMs / defaultStepDelayMs | ||
| if totalPoints < MinPoints { | ||
| totalPoints = MinPoints | ||
| } | ||
| } else { | ||
| totalPoints = int(math.Min( | ||
| float64(defaultMaxPoints)*float64(len(waypoints)-1), | ||
| math.Max(float64(MinPoints), math.Pow(totalDist, 0.25)*pathLengthScale*float64(len(waypoints)-1)))) | ||
| } | ||
|
|
||
| var allPoints [][2]int | ||
|
|
||
| for i := 0; i < len(waypoints)-1; i++ { | ||
| // Distribute points proportionally to segment distance. | ||
| var segPoints int | ||
| if totalDist > 0 { | ||
| segPoints = int(math.Round(float64(totalPoints) * segDistances[i] / totalDist)) | ||
| } else { | ||
| segPoints = totalPoints / (len(waypoints) - 1) | ||
| } | ||
| if segPoints < MinPoints { | ||
| segPoints = MinPoints | ||
| } | ||
| if segPoints > MaxPoints { | ||
| segPoints = MaxPoints | ||
| } | ||
|
|
||
| opts := &Options{MaxPoints: segPoints} | ||
| traj := NewHumanizeMouseTrajectoryWithOptions( | ||
| float64(waypoints[i][0]), float64(waypoints[i][1]), | ||
| float64(waypoints[i+1][0]), float64(waypoints[i+1][1]), | ||
| opts, | ||
| ) | ||
| segPts := traj.GetPointsInt() | ||
|
|
||
| if i == 0 { | ||
| allPoints = append(allPoints, segPts...) | ||
| } else { | ||
| // Skip first point of subsequent segments to avoid duplicates at junctions. | ||
| if len(segPts) > 1 { | ||
| allPoints = append(allPoints, segPts[1:]...) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Clamp to screen bounds. | ||
| clampPoints(allPoints, screenW, screenH) | ||
|
|
||
| stepDelay := defaultStepDelayMs | ||
| if totalDurationMs != nil && len(allPoints) > 1 { | ||
| stepDelay = *totalDurationMs / (len(allPoints) - 1) | ||
| if stepDelay < 3 { | ||
| stepDelay = 3 | ||
| } | ||
| } | ||
|
|
||
| return MultiSegmentResult{Points: allPoints, StepDelayMs: stepDelay} | ||
| } | ||
|
|
||
| // clampPoints constrains each point to [0, screenW-1] x [0, screenH-1]. | ||
| func clampPoints(points [][2]int, screenW, screenH int) { | ||
| maxX := screenW - 1 | ||
| maxY := screenH - 1 | ||
| for i := range points { | ||
| if points[i][0] < 0 { | ||
| points[i][0] = 0 | ||
| } else if points[i][0] > maxX { | ||
| points[i][0] = maxX | ||
| } | ||
| if points[i][1] < 0 { | ||
| points[i][1] = 0 | ||
| } else if points[i][1] > maxY { | ||
| points[i][1] = maxY | ||
| } | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicated
|
||
|
|
||
| const ( | ||
| // Bounds padding for Bezier control point region (pixels beyond start/end). | ||
| boundsPadding = 80 | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Late validation causes unintended mouse click on invalid input
Medium Severity
The
duration_msvalidation insidedoDragMouseSmoothruns after Phase 1 has already executedmousedown. When the validation fails, the parent error handler sendsmouseupas cleanup — effectively producing an unintended click at the start position before returning a 400 error. Theduration_msbounds check needs to happen before Phase 1'smousedownto avoid this side effect.Additional Locations (1)
server/cmd/api/api/computer.go#L910-L921