Count clicks ourselves so double-clicks work at all
Pixi only fills `detail` on the `click` event it synthesises; on `pointerdown` it is always 0, so every `e.detail === 2` branch was dead code. Asset rows, hierarchy folding and select-all in text fields never responded to a double-click. A shared click counter in src/ui/clicks.ts replaces the three readings of `e.detail`. A finished double-click ends the run, so two in a row count 2 and 2 rather than 3 and 4. The harness could only send `clickCount: 1`, which is why no test caught this: every "activate" check went through Enter. It can send real double-clicks now, and five checks drive the three paths separately.
This commit is contained in:
1 parent
d7c8d3e579
commit
ed1fe31c77
11 files changed
+196
-5
No files matched your search
@@ -181,6 +181,8 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
/** Rows the hierarchy shows – collapsed subtrees are not in here. */
|
||||
hierarchyOrder: () => hierarchy.rowOrder(),
|
||||
hierarchyChevron: (id: string) => hierarchy.chevronPosition(id),
|
||||
/** Centre of a hierarchy row, clear of the chevron – for clicks that must hit the row itself. */
|
||||
hierarchyRow: (id: string) => hierarchy.rowPosition(id),
|
||||
hierarchyRowColor: (id: string) => hierarchy.rowColor(id),
|
||||
hierarchyExpandAll: () => hierarchy.expandAll(),
|
||||
hierarchyCollapseAll: () => hierarchy.collapseAll(),
|
||||
|
||||
@@ -142,6 +142,11 @@ export class HierarchyPanel extends Panel {
|
||||
return this.list.chevronGlobal(id);
|
||||
}
|
||||
|
||||
/** Global centre of a row, away from its chevron (test hook). */
|
||||
rowPosition(id: string): { x: number; y: number } | null {
|
||||
return this.list.rowGlobal(id);
|
||||
}
|
||||
|
||||
expandAll(): void {
|
||||
for (const object of this.holder()?.objects ?? []) this.expanded.add(object.id);
|
||||
this.refresh();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { FederatedPointerEvent } from 'pixi.js';
|
||||
|
||||
/** Longest gap between two presses that still reads as one double-click. */
|
||||
const DOUBLE_MS = 400;
|
||||
/** How far the pointer may travel between them. */
|
||||
const SLOP = 6;
|
||||
|
||||
interface LastPress {
|
||||
key: unknown;
|
||||
button: number;
|
||||
time: number;
|
||||
x: number;
|
||||
y: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
let last: LastPress = { key: null, button: -1, time: 0, x: 0, y: 0, count: 0 };
|
||||
|
||||
/**
|
||||
* How often `key` was pressed in a row with the same button – 2 on a double-click. Pixi counts
|
||||
* clicks only for the `click`/`pointertap` it synthesises and leaves `detail` at 0 on
|
||||
* `pointerdown`, so a press handler reading `e.detail` never sees a second click.
|
||||
*
|
||||
* `key` identifies what was hit and should survive a rebuild of the widget it belongs to: list
|
||||
* rows are thrown away and recreated whenever the list refreshes, which a selection – the first
|
||||
* click – can trigger.
|
||||
*/
|
||||
export function pressCount(e: FederatedPointerEvent, key: unknown): number {
|
||||
const gap = e.timeStamp - last.time;
|
||||
const repeat =
|
||||
key === last.key &&
|
||||
e.button === last.button &&
|
||||
gap >= 0 &&
|
||||
gap <= DOUBLE_MS &&
|
||||
Math.hypot(e.global.x - last.x, e.global.y - last.y) <= SLOP;
|
||||
const count = repeat ? last.count + 1 : 1;
|
||||
last = {
|
||||
key,
|
||||
button: e.button,
|
||||
time: e.timeStamp,
|
||||
x: e.global.x,
|
||||
y: e.global.y,
|
||||
// A finished double-click ends the run: folding a row open and shut again is two
|
||||
// double-clicks, and the second must count as one rather than as clicks three and four.
|
||||
count: count === 2 ? 0 : count,
|
||||
};
|
||||
return count;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type FederatedPointerEvent, Graphics, Text } from 'pixi.js';
|
||||
import { pressCount } from '../clicks';
|
||||
import { centreY } from '../math';
|
||||
import { Theme, textStyle } from '../Theme';
|
||||
import { type Size, Widget } from '../Widget';
|
||||
@@ -223,7 +224,7 @@ export class ListView extends Widget {
|
||||
// being dragged into.
|
||||
if (!this.onDragOut && (!this.highlighted.has(item.id) || item.id === this.selectedId))
|
||||
this.select(item.id);
|
||||
if (e.detail === 2) this.onActivate?.(item.id);
|
||||
if (pressCount(e, item.id) === 2) this.onActivate?.(item.id);
|
||||
else if (this.onDrop || this.onDragOut) {
|
||||
this.drag = { id: item.id, startX: e.global.x, startY: e.global.y, active: false, spot: null };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CanvasTextMetrics, type FederatedPointerEvent, Graphics, Text } from 'pixi.js';
|
||||
import { pressCount } from '../clicks';
|
||||
import { drawControlBox } from '../draw';
|
||||
import type { TextEditTarget } from '../KeyboardBridge';
|
||||
import { centreY, clamp } from '../math';
|
||||
@@ -101,11 +102,12 @@ export class TextField extends Widget implements TextEditTarget {
|
||||
if (e.button !== 0) return;
|
||||
e.stopPropagation();
|
||||
const index = this.indexAt(this.toLocal(e.global).x);
|
||||
const presses = pressCount(e, this);
|
||||
if (!this.editing) {
|
||||
ui().focus.focus(this);
|
||||
if (e.detail >= 2) this.selectAll();
|
||||
if (presses >= 2) this.selectAll();
|
||||
else this.setSelection(index, index, true);
|
||||
} else if (e.detail >= 2) {
|
||||
} else if (presses >= 2) {
|
||||
this.selectAll();
|
||||
} else {
|
||||
this.setSelection(index, index, true);
|
||||
|
||||
Reference in new issue
Block a user