Skip to content

Commit 4602966

Browse files
committed
feat(interaction): toleranceRate parameter added to click-based interactions
1 parent a199b51 commit 4602966

File tree

7 files changed

+694
-477
lines changed

7 files changed

+694
-477
lines changed

src/impl/interaction/library/Click.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,34 @@ export class ClickFSM extends FSMImpl {
3030
* Creates the FSM
3131
* @param logger - The logger to use for this interaction
3232
* @param action - The action executed on a click
33+
* @param toleranceMove - The accepted number of pixel moves between the pressure and the release of the click
3334
*/
34-
public constructor(logger: Logger, action?: (evt: MouseEvent) => void) {
35+
public constructor(logger: Logger, action?: (evt: MouseEvent) => void, toleranceMove?: number) {
3536
super(logger);
3637

3738
const down = this.addStdState("down");
3839
const clicked = this.addTerminalState("clicked");
3940

4041
this.startingState = clicked;
4142

43+
let firstX = 0;
44+
let firstY = 0;
45+
4246
new MouseTransition(this.initState, down, "mousedown",
4347
(evt: MouseEvent): void => {
48+
firstX = evt.clientX;
49+
firstY = evt.clientY;
4450
this.setCheckButton(evt.button);
4551
},
4652
(evt: MouseEvent): boolean => this.checkButton === undefined || evt.button === this.checkButton);
4753

4854
new MouseTransition(down, clicked, "mouseup", action,
4955
(evt: MouseEvent): boolean => this.checkButton === undefined || evt.button === this.checkButton);
56+
57+
if (toleranceMove !== undefined) {
58+
new MouseTransition(down, this.addCancellingState("moved"), "mousemove", undefined,
59+
evt => Math.abs(firstX - evt.clientX) > toleranceMove || Math.abs(firstY - evt.clientY) > toleranceMove);
60+
}
5061
}
5162

5263
public getCheckButton(): number {
@@ -76,11 +87,12 @@ export class Click extends InteractionBase<PointData, PointDataImpl> {
7687
* @param fsm - The optional FSM provided for the interaction
7788
* @param data - The interaction data to use
7889
* @param name - The name of the user interaction
90+
* @param toleranceMove - The accepted number of pixel moves between the pressure and the release of the click
7991
*/
80-
public constructor(logger: Logger, fsm?: ClickFSM, data?: PointDataImpl, name?: string) {
92+
public constructor(logger: Logger, fsm?: ClickFSM, data?: PointDataImpl, name?: string, toleranceMove?: number) {
8193
const theFSM = fsm ?? new ClickFSM(logger, (evt: MouseEvent): void => {
8294
this._data.copy(evt);
83-
});
95+
}, toleranceMove);
8496
super(theFSM, data ?? new PointDataImpl(), logger, name ?? Click.name);
8597
}
8698
}

src/impl/interaction/library/DoubleClick.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,18 @@ export class DoubleClickFSM extends FSMImpl {
5959

6060
private checkButton: number | undefined;
6161

62-
public constructor(logger: Logger, subAction?: (evt: MouseEvent) => void) {
62+
public constructor(logger: Logger, subAction?: (evt: MouseEvent) => void, toleranceMove?: number) {
6363
super(logger);
64-
this.firstClickFSM = new ClickFSM(logger, subAction);
65-
this.sndClickFSM = new ClickFSM(logger);
64+
this.firstClickFSM = new ClickFSM(logger, subAction, toleranceMove);
65+
this.sndClickFSM = new ClickFSM(logger, undefined, toleranceMove);
6666

6767
const errorHandler = {
6868
"fsmError": (err: unknown): void => {
6969
this.notifyHandlerOnError(err);
7070
}
7171
};
72+
let firstX = 0;
73+
let firstY = 0;
7274

7375
this.firstClickFSM.addHandler(errorHandler);
7476
this.sndClickFSM.addHandler(errorHandler);
@@ -77,12 +79,18 @@ export class DoubleClickFSM extends FSMImpl {
7779
const clicked = this.addStdState("clicked");
7880

7981
new SubFSMTransitionImpl(this.initState, clicked, this.firstClickFSM,
80-
(): void => {
82+
(evt: Event): void => {
8183
this.setCheckButton(this.firstClickFSM.getCheckButton());
84+
if (evt instanceof MouseEvent) {
85+
firstX = evt.clientX;
86+
firstY = evt.clientY;
87+
}
8288
});
8389

84-
new MouseTransition(clicked, cancelled, "mousemove", undefined,
85-
(ev: Event): boolean => (this.checkButton === undefined || ev instanceof MouseEvent && ev.button === this.checkButton));
90+
if (toleranceMove !== undefined) {
91+
new MouseTransition(clicked, cancelled, "mousemove", undefined,
92+
(evt: MouseEvent): boolean => Math.abs(firstX - evt.clientX) > toleranceMove || Math.abs(firstY - evt.clientY) > toleranceMove);
93+
}
8694

8795
new TimeoutTransition(clicked, cancelled, DoubleClickFSM.timeGapSupplier);
8896
new SubFSMTransitionImpl(clicked, this.addTerminalState("dbleclicked", true), this.sndClickFSM);
@@ -125,10 +133,18 @@ export class DoubleClickFSM extends FSMImpl {
125133
* @category Interaction Library
126134
*/
127135
export class DoubleClick extends InteractionBase<PointData, PointDataImpl> {
128-
public constructor(logger: Logger, fsm?: DoubleClickFSM, data?: PointDataImpl, name?: string) {
136+
/**
137+
* Creates the interaction
138+
* @param logger - The logger to use for this interaction
139+
* @param fsm - The optional FSM provided for the interaction
140+
* @param data - The interaction data to use
141+
* @param name - The name of the user interaction
142+
* @param toleranceMove - The accepted number of pixel moves between the pressure and the release of each click
143+
*/
144+
public constructor(logger: Logger, fsm?: DoubleClickFSM, data?: PointDataImpl, name?: string, toleranceMove?: number) {
129145
const theFSM = fsm ?? new DoubleClickFSM(logger, (evt: MouseEvent): void => {
130146
this._data.copy(evt);
131-
});
147+
}, toleranceMove);
132148
super(theFSM, data ?? new PointDataImpl(), logger, name ?? DoubleClick.name);
133149
/*
134150
* We give the interaction to the first click as this click interaction

src/impl/interaction/library/DragLock.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ class DragLockFSM extends FSMImpl {
3636
protected checkButton: number | undefined;
3737

3838
public constructor(logger: Logger, handler: DragLockFSMHandler,
39-
dbleClick1Action: (evt: MouseEvent) => void, dbleClick2Action: (evt: MouseEvent) => void) {
39+
dbleClick1Action: (evt: MouseEvent) => void,
40+
dbleClick2Action: (evt: MouseEvent) => void, toleranceMove?: number) {
4041
super(logger);
41-
this.firstDbleClick = new DoubleClickFSM(logger, dbleClick1Action);
42-
this.sndDbleClick = new DoubleClickFSM(logger, dbleClick2Action);
42+
this.firstDbleClick = new DoubleClickFSM(logger, dbleClick1Action, toleranceMove);
43+
this.sndDbleClick = new DoubleClickFSM(logger, dbleClick2Action, toleranceMove);
4344

4445
const cancelDbleClick = new DoubleClickFSM(logger);
4546
const errorHandler = {
@@ -108,8 +109,9 @@ export class DragLock extends InteractionBase<SrcTgtPointsData<PointData>, SrcTg
108109
* Creates a drag lock.
109110
* @param logger - The logger to use for this interaction
110111
* @param name - The name of the user interaction
112+
* @param toleranceMove - The accepted number of pixel moves between the pressure and the release of the click
111113
*/
112-
public constructor(logger: Logger, name?: string) {
114+
public constructor(logger: Logger, name?: string, toleranceMove?: number) {
113115
const handler: DragLockFSMHandler = {
114116
"onMove": (evt: MouseEvent): void => {
115117
this._data.tgt.copy(evt);
@@ -124,7 +126,7 @@ export class DragLock extends InteractionBase<SrcTgtPointsData<PointData>, SrcTg
124126
this._data.src.copy(evt);
125127
}, (evt: MouseEvent): void => {
126128
this._data.tgt.copy(evt);
127-
});
129+
}, toleranceMove);
128130

129131
super(theFSM, new SrcTgtPointsDataImpl(), logger, name ?? DragLock.name);
130132

test/fsm/VisitorFSMDepthFirst.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe("using the std FSM visitor implementation", () => {
9696
test("visiting the double click FSM works", () => {
9797
visitedFSM = new DoubleClickFSM(mock<Logger>());
9898
visitedFSM.acceptVisitor(visitor);
99-
expect(visitor.res).toBe(">i[init]>i[init]-mousedown-s[down]-mouseup-t[clicked]s[clicked]-mousemove-c[cancelled]-timeout-c[cancelled]>i[init]-mousedown-s[down]-mouseup-t[clicked]t[dbleclicked]");
99+
expect(visitor.res).toBe(">i[init]>i[init]-mousedown-s[down]-mouseup-t[clicked]s[clicked]-timeout-c[cancelled]>i[init]-mousedown-s[down]-mouseup-t[clicked]t[dbleclicked]");
100100
});
101101

102102
test("visiting the non-cancellable DnD FSM works", () => {

0 commit comments

Comments
 (0)