Skip to content

Commit d8fdd5b

Browse files
Normalize pointer movement across resolutions (#224)
Co-authored-by: Owen McGirr <o.a.mcgirr@gmail.com>
1 parent c637f0c commit d8fdd5b

2 files changed

Lines changed: 63 additions & 21 deletions

File tree

src/main/input/pointer-profile.test.ts

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MAX_POINTER_DELTA } from '../../shared/protocol';
33
import { createPointerMovementProfile } from './pointer-profile';
44

55
describe('createPointerMovementProfile', () => {
6-
it('creates sensible deltas for a 1280x720 display at 1.5 scale', () => {
6+
it('creates display-relative deltas for a 1280x720 display at 1.5 scale', () => {
77
const profile = createPointerMovementProfile({
88
cursor: { x: 100, y: 100 },
99
display: {
@@ -18,17 +18,17 @@ describe('createPointerMovementProfile', () => {
1818
bounds: { x: 0, y: 0, width: 1280, height: 720 },
1919
maxDelta: MAX_POINTER_DELTA,
2020
recommendedDeltas: {
21-
small: 32,
22-
medium: 85,
23-
large: 187
21+
small: 21,
22+
medium: 57,
23+
large: 125
2424
},
2525
capabilities: {
2626
noAckMouseMove: true
2727
}
2828
});
2929
});
3030

31-
it('returns native target deltas on a 1x display', () => {
31+
it('preserves current feel on a 1920x1080 display at 1x scale', () => {
3232
expect(
3333
createPointerMovementProfile({
3434
cursor: { x: 100, y: 100 },
@@ -38,25 +38,57 @@ describe('createPointerMovementProfile', () => {
3838
}
3939
}).recommendedDeltas
4040
).toEqual({
41-
small: 48,
42-
medium: 128,
43-
large: 280
41+
small: 49,
42+
medium: 130,
43+
large: 281
4444
});
4545
});
4646

47-
it('returns small logical deltas on a 3x display', () => {
47+
it('returns larger clamped deltas on a 4K display at 1x scale', () => {
4848
expect(
4949
createPointerMovementProfile({
5050
cursor: { x: 100, y: 100 },
5151
display: {
52-
bounds: { x: 0, y: 0, width: 1920, height: 960 },
53-
scaleFactor: 3
52+
bounds: { x: 0, y: 0, width: 3840, height: 2160 },
53+
scaleFactor: 1
54+
}
55+
}).recommendedDeltas
56+
).toEqual({
57+
small: 97,
58+
medium: 259,
59+
large: MAX_POINTER_DELTA
60+
});
61+
});
62+
63+
it('uses the short edge for ultrawide displays', () => {
64+
expect(
65+
createPointerMovementProfile({
66+
cursor: { x: 100, y: 100 },
67+
display: {
68+
bounds: { x: 0, y: 0, width: 3440, height: 1440 },
69+
scaleFactor: 1
70+
}
71+
}).recommendedDeltas
72+
).toEqual({
73+
small: 65,
74+
medium: 173,
75+
large: 374
76+
});
77+
});
78+
79+
it('still divides by scale factor on high-DPI displays', () => {
80+
expect(
81+
createPointerMovementProfile({
82+
cursor: { x: 100, y: 100 },
83+
display: {
84+
bounds: { x: 0, y: 0, width: 3840, height: 2160 },
85+
scaleFactor: 2
5486
}
5587
}).recommendedDeltas
5688
).toEqual({
57-
small: 16,
58-
medium: 43,
59-
large: 93
89+
small: 49,
90+
medium: 130,
91+
large: 281
6092
});
6193
});
6294

src/main/input/pointer-profile.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { MAX_POINTER_DELTA, NO_ACK_CONTROL_COMMAND_TYPES, type PointerMovementPr
33
type Point = { x: number; y: number };
44
type Bounds = { x: number; y: number; width: number; height: number };
55

6-
const TARGET_NATIVE_DELTAS = {
7-
small: 48,
8-
medium: 128,
9-
large: 280
6+
const TARGET_DISPLAY_FRACTIONS = {
7+
small: 0.045,
8+
medium: 0.12,
9+
large: 0.26
1010
};
1111

1212
export function createPointerMovementProfile(input: {
@@ -21,16 +21,17 @@ export function createPointerMovementProfile(input: {
2121
const scaleFactor =
2222
Number.isFinite(input.display.scaleFactor) && input.display.scaleFactor > 0 ? input.display.scaleFactor : 1;
2323
const maxDelta = input.maxDelta ?? MAX_POINTER_DELTA;
24+
const targetNativeDeltas = targetNativeDeltasForDisplay(bounds);
2425

2526
return {
2627
displayId: `${bounds.x}:${bounds.y}:${bounds.width}:${bounds.height}:${scaleFactor}`,
2728
scaleFactor,
2829
bounds,
2930
maxDelta,
3031
recommendedDeltas: {
31-
small: toLogicalDelta(TARGET_NATIVE_DELTAS.small, scaleFactor, maxDelta),
32-
medium: toLogicalDelta(TARGET_NATIVE_DELTAS.medium, scaleFactor, maxDelta),
33-
large: toLogicalDelta(TARGET_NATIVE_DELTAS.large, scaleFactor, maxDelta)
32+
small: toLogicalDelta(targetNativeDeltas.small, scaleFactor, maxDelta),
33+
medium: toLogicalDelta(targetNativeDeltas.medium, scaleFactor, maxDelta),
34+
large: toLogicalDelta(targetNativeDeltas.large, scaleFactor, maxDelta)
3435
},
3536
capabilities: {
3637
noAckMouseMove: true,
@@ -39,6 +40,15 @@ export function createPointerMovementProfile(input: {
3940
};
4041
}
4142

43+
function targetNativeDeltasForDisplay(bounds: Bounds): { small: number; medium: number; large: number } {
44+
const referenceSize = Math.min(bounds.width, bounds.height);
45+
return {
46+
small: Math.round(referenceSize * TARGET_DISPLAY_FRACTIONS.small),
47+
medium: Math.round(referenceSize * TARGET_DISPLAY_FRACTIONS.medium),
48+
large: Math.round(referenceSize * TARGET_DISPLAY_FRACTIONS.large)
49+
};
50+
}
51+
4252
function normalizeBounds(bounds: Bounds): Bounds {
4353
return {
4454
x: finiteOr(bounds.x, 0),

0 commit comments

Comments
 (0)