@gtkx/testing 1.0.0-rc.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/dist/accessible-native.d.ts +16 -0
- package/dist/accessible-native.d.ts.map +1 -0
- package/dist/accessible-native.js +206 -0
- package/dist/accessible-native.js.map +1 -0
- package/dist/act.d.ts +2 -0
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js.map +1 -1
- package/dist/build-queries.d.ts +27 -10
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +39 -30
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +1 -1
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -3
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +35 -6
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +23 -7
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -1
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +15 -5
- package/dist/frame-sync.js.map +1 -1
- package/dist/harness-window.d.ts +4 -0
- package/dist/harness-window.d.ts.map +1 -0
- package/dist/harness-window.js +6 -0
- package/dist/harness-window.js.map +1 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -0
- package/dist/matchers.d.ts +125 -39
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +156 -18
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-format.d.ts +2 -0
- package/dist/pretty-format.d.ts.map +1 -0
- package/dist/pretty-format.js +2 -0
- package/dist/pretty-format.js.map +1 -0
- package/dist/pretty-widget.d.ts +7 -3
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +74 -57
- package/dist/pretty-widget.js.map +1 -1
- package/dist/production.d.ts +8 -0
- package/dist/production.d.ts.map +1 -0
- package/dist/production.js +34 -0
- package/dist/production.js.map +1 -0
- package/dist/queries.d.ts +99 -56
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +123 -83
- package/dist/queries.js.map +1 -1
- package/dist/query-helpers.d.ts +44 -0
- package/dist/query-helpers.d.ts.map +1 -0
- package/dist/query-helpers.js +57 -0
- package/dist/query-helpers.js.map +1 -0
- package/dist/render-children.d.ts +9 -0
- package/dist/render-children.d.ts.map +1 -0
- package/dist/render-children.js +11 -0
- package/dist/render-children.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +13 -11
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +12 -3
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +13 -4
- package/dist/role-helpers.js.map +1 -1
- package/dist/role-naming.d.ts +5 -0
- package/dist/role-naming.d.ts.map +1 -0
- package/dist/role-naming.js +96 -0
- package/dist/role-naming.js.map +1 -0
- package/dist/screenshot.d.ts +12 -20
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +183 -91
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +3 -0
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +9 -1
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +26 -6
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +67 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +16 -0
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +34 -2
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +45 -3
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +177 -39
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/column-header-click.d.ts +4 -0
- package/dist/user-event/column-header-click.d.ts.map +1 -0
- package/dist/user-event/column-header-click.js +65 -0
- package/dist/user-event/column-header-click.js.map +1 -0
- package/dist/user-event/controller.d.ts +29 -0
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +28 -0
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +2 -1
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/gesture.d.ts +51 -0
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +47 -4
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +70 -2
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +43 -26
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/indexed-children.d.ts +10 -0
- package/dist/user-event/indexed-children.d.ts.map +1 -0
- package/dist/user-event/indexed-children.js +50 -0
- package/dist/user-event/indexed-children.js.map +1 -0
- package/dist/user-event/keyboard.d.ts +4 -2
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +46 -16
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts +4 -0
- package/dist/user-event/list-row-click.d.ts.map +1 -0
- package/dist/user-event/list-row-click.js +39 -0
- package/dist/user-event/list-row-click.js.map +1 -0
- package/dist/user-event/native-click.d.ts +6 -0
- package/dist/user-event/native-click.d.ts.map +1 -0
- package/dist/user-event/native-click.js +32 -0
- package/dist/user-event/native-click.js.map +1 -0
- package/dist/user-event/pointer.d.ts +1 -1
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +37 -13
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +18 -0
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +56 -30
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/state.d.ts +1 -1
- package/dist/user-event/state.d.ts.map +1 -1
- package/dist/user-event/state.js +1 -1
- package/dist/user-event/state.js.map +1 -1
- package/dist/user-event/text.d.ts +34 -3
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +94 -17
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +2 -0
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +14 -10
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +135 -173
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/widget-getters.d.ts +8 -0
- package/dist/widget-getters.d.ts.map +1 -0
- package/dist/widget-getters.js +19 -0
- package/dist/widget-getters.js.map +1 -0
- package/dist/within.d.ts +3 -2
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +3 -2
- package/dist/within.js.map +1 -1
- package/package.json +13 -8
- package/src/accessible-native.ts +333 -0
- package/src/act.ts +2 -0
- package/src/build-queries.ts +66 -48
- package/src/cleanup-registry.ts +1 -1
- package/src/config.ts +4 -3
- package/src/errors.ts +14 -3
- package/src/fire-event.ts +61 -8
- package/src/frame-sync.ts +21 -5
- package/src/harness-window.ts +9 -0
- package/src/index.ts +18 -11
- package/src/internal.ts +11 -0
- package/src/matchers.ts +466 -64
- package/src/pretty-format.ts +1 -0
- package/src/pretty-widget.ts +100 -72
- package/src/production.ts +52 -0
- package/src/queries.ts +128 -89
- package/src/query-helpers.ts +97 -0
- package/src/render-children.ts +25 -0
- package/src/render.tsx +18 -13
- package/src/role-helpers.ts +14 -4
- package/src/role-naming.ts +99 -0
- package/src/screenshot.ts +272 -103
- package/src/suggestions.ts +3 -0
- package/src/traversal.ts +49 -6
- package/src/types.ts +69 -19
- package/src/user-event/adjustment.ts +42 -2
- package/src/user-event/click.ts +248 -46
- package/src/user-event/column-header-click.ts +90 -0
- package/src/user-event/controller.ts +29 -0
- package/src/user-event/event-wrapper.ts +3 -1
- package/src/user-event/gesture.ts +57 -6
- package/src/user-event/index.ts +119 -29
- package/src/user-event/indexed-children.ts +75 -0
- package/src/user-event/keyboard.ts +70 -20
- package/src/user-event/list-row-click.ts +57 -0
- package/src/user-event/native-click.ts +46 -0
- package/src/user-event/pointer.ts +52 -13
- package/src/user-event/selection.ts +74 -33
- package/src/user-event/state.ts +2 -2
- package/src/user-event/text.ts +121 -23
- package/src/wait-for.ts +2 -0
- package/src/widget-accessible-properties.ts +183 -214
- package/src/widget-getters.ts +32 -0
- package/src/within.ts +3 -2
package/src/user-event/click.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
import * as Gdk from "@gtkx/gi/gdk";
|
|
1
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
import {
|
|
3
|
+
import { hasSignalListener } from "@gtkx/runtime/internal";
|
|
4
|
+
import { callBooleanGetter, getCallableMethod, hasWidgetMethod } from "../widget-getters.js";
|
|
5
|
+
import { queryAllControllers } from "./controller.js";
|
|
3
6
|
import { wrapEvent } from "./event-wrapper.js";
|
|
7
|
+
import { hasIndexedChildren, selectContainerChild, SELECTED_PROBE, unselectOtherChildren } from "./indexed-children.js";
|
|
8
|
+
import { isClickTransparent, type NativeClick, nativeClickFor } from "./native-click.js";
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
type PressPoint = { x: number; y: number };
|
|
11
|
+
type ClickPhase = "pressed" | "released";
|
|
12
|
+
type ClickSite = { gestures: Gtk.GestureClick[]; point: PressPoint };
|
|
13
|
+
type ClickScope = { isClicked: boolean; isInternalAllowed: boolean };
|
|
14
|
+
|
|
15
|
+
type ClickTarget = {
|
|
16
|
+
widget: Gtk.Widget;
|
|
17
|
+
container: Gtk.Widget | null;
|
|
18
|
+
gestures: Gtk.GestureClick[];
|
|
19
|
+
isClaiming: boolean;
|
|
20
|
+
native: NativeClick | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const CLICK_SIGNALS = ["pressed", "released"];
|
|
24
|
+
const PRIMARY_BUTTONS: Set<number> = new Set([0, Gdk.BUTTON_PRIMARY]);
|
|
25
|
+
|
|
26
|
+
const getCenterPoint = (widget: Gtk.Widget): PressPoint => {
|
|
6
27
|
const width = widget.getWidth();
|
|
7
28
|
const height = widget.getHeight();
|
|
8
29
|
|
|
@@ -13,87 +34,268 @@ const getPressPoint = (widget: Gtk.Widget): { x: number; y: number } => {
|
|
|
13
34
|
return { x: 0, y: 0 };
|
|
14
35
|
};
|
|
15
36
|
|
|
16
|
-
const
|
|
17
|
-
|
|
37
|
+
const getClickPoint = (clicked: Gtk.Widget, carrier: Gtk.Widget): PressPoint => {
|
|
38
|
+
if (clicked === carrier) {
|
|
39
|
+
return getCenterPoint(clicked);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [isComputed, bounds] = clicked.computeBounds(carrier);
|
|
18
43
|
|
|
44
|
+
if (!isComputed) {
|
|
45
|
+
return getCenterPoint(carrier);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { x: bounds.getX() + bounds.getWidth() / 2, y: bounds.getY() + bounds.getHeight() / 2 };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const emitGesture = (point: PressPoint, controllers: Gtk.GestureClick[], nPress: number, signal: string): void => {
|
|
19
52
|
for (const controller of controllers) {
|
|
20
|
-
controller.emit(signal, nPress, x, y);
|
|
53
|
+
controller.emit(signal, nPress, point.x, point.y);
|
|
21
54
|
}
|
|
22
55
|
};
|
|
23
56
|
|
|
24
|
-
const
|
|
25
|
-
|
|
57
|
+
const emitClickPhase = (
|
|
58
|
+
widget: Gtk.Widget,
|
|
59
|
+
controllers: Gtk.GestureClick[],
|
|
60
|
+
nPress: number,
|
|
61
|
+
phase: ClickPhase,
|
|
62
|
+
): void => {
|
|
63
|
+
emitGesture(getCenterPoint(widget), controllers, nPress, phase);
|
|
26
64
|
};
|
|
27
65
|
|
|
28
|
-
const
|
|
29
|
-
|
|
66
|
+
const emitSitePhase = (sites: ClickSite[], nPress: number, phase: ClickPhase): void => {
|
|
67
|
+
for (const site of sites) {
|
|
68
|
+
emitGesture(site.point, site.gestures, nPress, phase);
|
|
69
|
+
}
|
|
30
70
|
};
|
|
31
71
|
|
|
32
|
-
const emitClickSequence = (
|
|
33
|
-
|
|
34
|
-
|
|
72
|
+
const emitClickSequence = (sites: ClickSite[], nPress: number): void => {
|
|
73
|
+
for (let i = 1; i <= nPress; i++) {
|
|
74
|
+
emitSitePhase(sites, i, "pressed");
|
|
75
|
+
emitSitePhase(sites, i, "released");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
35
78
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
emitRelease(target, controllers, i);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
79
|
+
const containerFor = (widget: Gtk.Widget): Gtk.Widget | null => {
|
|
80
|
+
const parent = widget.getParent();
|
|
41
81
|
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
return parent !== null && hasWidgetMethod(widget, SELECTED_PROBE) && hasIndexedChildren(parent) ? parent : null;
|
|
83
|
+
};
|
|
44
84
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
85
|
+
const hasClickHandlers = (gesture: Gtk.GestureClick): boolean => hasSignalListener(gesture, CLICK_SIGNALS);
|
|
86
|
+
const isInternalGesture = (gesture: Gtk.GestureClick): boolean => !hasSignalListener(gesture);
|
|
87
|
+
const isPrimaryGesture = (gesture: Gtk.GestureClick): boolean => PRIMARY_BUTTONS.has(gesture.getButton());
|
|
49
88
|
|
|
50
|
-
|
|
89
|
+
const clickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
|
|
90
|
+
queryAllControllers(widget, Gtk.GestureClick).filter((gesture) => isPrimaryGesture(gesture));
|
|
91
|
+
|
|
92
|
+
const getAuthoredClickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
|
|
93
|
+
clickGestures(widget).filter((gesture) => hasClickHandlers(gesture));
|
|
94
|
+
|
|
95
|
+
const isClaimingTarget = (widget: Gtk.Widget): boolean =>
|
|
96
|
+
widget instanceof Gtk.Button || containerFor(widget) !== null;
|
|
97
|
+
|
|
98
|
+
const gestureTargetFor = (widget: Gtk.Widget, isInternalAllowed: boolean): ClickTarget | null => {
|
|
99
|
+
const gestures = clickGestures(widget);
|
|
100
|
+
|
|
101
|
+
if (isClaimingTarget(widget)) {
|
|
102
|
+
return { widget, container: containerFor(widget), gestures, isClaiming: true, native: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const authored = gestures.filter((gesture) => hasClickHandlers(gesture));
|
|
106
|
+
|
|
107
|
+
if (authored.length > 0) {
|
|
108
|
+
return { widget, container: null, gestures: authored, isClaiming: false, native: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isInternalAllowed && gestures.some((gesture) => isInternalGesture(gesture))) {
|
|
112
|
+
return { widget, container: null, gestures, isClaiming: true, native: null };
|
|
51
113
|
}
|
|
52
114
|
|
|
53
115
|
return null;
|
|
54
116
|
};
|
|
55
117
|
|
|
56
|
-
const
|
|
57
|
-
if (widget
|
|
58
|
-
return
|
|
118
|
+
const clickTargetFor = (widget: Gtk.Widget, scope: ClickScope): ClickTarget | null => {
|
|
119
|
+
if (isClickTransparent(widget)) {
|
|
120
|
+
return null;
|
|
59
121
|
}
|
|
60
122
|
|
|
61
|
-
|
|
123
|
+
const native = nativeClickFor(widget, scope.isClicked);
|
|
62
124
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
125
|
+
if (native !== null) {
|
|
126
|
+
return { widget, container: null, gestures: getAuthoredClickGestures(widget), isClaiming: true, native };
|
|
127
|
+
}
|
|
66
128
|
|
|
67
|
-
return
|
|
129
|
+
return gestureTargetFor(widget, scope.isInternalAllowed);
|
|
68
130
|
};
|
|
69
131
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
132
|
+
const isSelfClickTarget = (widget: Gtk.Widget): boolean =>
|
|
133
|
+
isClickTransparent(widget) || clickTargetFor(widget, { isClicked: true, isInternalAllowed: false }) !== null;
|
|
134
|
+
|
|
135
|
+
const nextClickWidget = (current: Gtk.Widget, target: ClickTarget | null): Gtk.Widget | null =>
|
|
136
|
+
target?.isClaiming === true ? null : current.getParent();
|
|
137
|
+
|
|
138
|
+
const collectClickTargets = (widget: Gtk.Widget): ClickTarget[] => {
|
|
139
|
+
const targets: ClickTarget[] = [];
|
|
140
|
+
let current: Gtk.Widget | null = widget;
|
|
141
|
+
|
|
142
|
+
while (current !== null) {
|
|
143
|
+
const isClicked = current === widget;
|
|
144
|
+
const scope = { isClicked, isInternalAllowed: !isClicked && targets.length === 0 };
|
|
145
|
+
const target = clickTargetFor(current, scope);
|
|
146
|
+
const next = nextClickWidget(current, target);
|
|
147
|
+
|
|
148
|
+
if (target !== null) {
|
|
149
|
+
targets.push(target);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
current = next;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return targets;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const targetSites = (clicked: Gtk.Widget, target: ClickTarget): ClickSite[] => {
|
|
159
|
+
const { container } = target;
|
|
160
|
+
const site = { gestures: target.gestures, point: getClickPoint(clicked, target.widget) };
|
|
161
|
+
|
|
162
|
+
if (container === null) {
|
|
163
|
+
return [site];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [site, { gestures: getAuthoredClickGestures(container), point: getClickPoint(clicked, container) }];
|
|
167
|
+
};
|
|
73
168
|
|
|
169
|
+
const isSingleClickActivating = (container: Gtk.Widget): boolean =>
|
|
170
|
+
callBooleanGetter(container, "getActivateOnSingleClick") ?? true;
|
|
171
|
+
|
|
172
|
+
const isMultipleSelection = (container: Gtk.Widget): boolean =>
|
|
173
|
+
getCallableMethod<[], Gtk.SelectionMode>(container, "getSelectionMode")?.() === Gtk.SelectionMode.MULTIPLE;
|
|
174
|
+
|
|
175
|
+
const isChildSelectable = (child: Gtk.Widget): boolean => callBooleanGetter(child, "getSelectable") ?? true;
|
|
176
|
+
|
|
177
|
+
const replaceContainerSelection = (container: Gtk.Widget, child: Gtk.Widget): void => {
|
|
178
|
+
if (!isChildSelectable(child)) {
|
|
74
179
|
return;
|
|
75
180
|
}
|
|
76
181
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
182
|
+
selectContainerChild(container, child);
|
|
183
|
+
|
|
184
|
+
if (isMultipleSelection(container)) {
|
|
185
|
+
unselectOtherChildren(container, child);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
81
188
|
|
|
189
|
+
const isActivatedByClick = (container: Gtk.Widget, nPress: number): boolean =>
|
|
190
|
+
isSingleClickActivating(container) || nPress > 1;
|
|
191
|
+
|
|
192
|
+
const isSelectionReplacedByClick = (container: Gtk.Widget, nPress: number): boolean =>
|
|
193
|
+
nPress > 1 || !isSingleClickActivating(container);
|
|
194
|
+
|
|
195
|
+
const applyContainerOutcome = (target: ClickTarget, nPress: number): void => {
|
|
196
|
+
const { container, widget } = target;
|
|
197
|
+
|
|
198
|
+
if (container === null || widget.getParent() !== container) {
|
|
82
199
|
return;
|
|
83
200
|
}
|
|
84
201
|
|
|
85
|
-
if (
|
|
202
|
+
if (isActivatedByClick(container, nPress)) {
|
|
203
|
+
widget.activate();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (isSelectionReplacedByClick(container, nPress)) {
|
|
207
|
+
replaceContainerSelection(container, widget);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const applyClickOutcome = (target: ClickTarget, nPress: number): void => {
|
|
212
|
+
if (target.native === null) {
|
|
213
|
+
applyContainerOutcome(target, nPress);
|
|
214
|
+
|
|
86
215
|
return;
|
|
87
216
|
}
|
|
88
217
|
|
|
89
|
-
|
|
218
|
+
target.native(target.widget, nPress);
|
|
219
|
+
};
|
|
90
220
|
|
|
91
|
-
|
|
92
|
-
|
|
221
|
+
const applyClickOutcomes = (targets: ClickTarget[], nPress: number): void => {
|
|
222
|
+
for (const target of targets) {
|
|
223
|
+
applyClickOutcome(target, nPress);
|
|
93
224
|
}
|
|
94
225
|
};
|
|
95
226
|
|
|
96
|
-
const
|
|
97
|
-
|
|
227
|
+
const deliverClick = (widget: Gtk.Widget, nPress: number): Promise<void> =>
|
|
228
|
+
wrapEvent(widget, () => {
|
|
229
|
+
const targets = collectClickTargets(widget);
|
|
230
|
+
const sites = targets.flatMap((target) => targetSites(widget, target));
|
|
231
|
+
emitClickSequence(sites, nPress);
|
|
232
|
+
applyClickOutcomes(targets, nPress);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
/* eslint-disable-next-line unicorn/consistent-boolean-name -- the boolean reports whether activation succeeded */
|
|
236
|
+
const tryActivate = async (widget: Gtk.Widget): Promise<boolean> => {
|
|
237
|
+
if (widget.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let isActivated = false;
|
|
242
|
+
|
|
243
|
+
await wrapEvent(widget, () => {
|
|
244
|
+
isActivated = widget.activate();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return isActivated;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Activates a widget that neither claims the click nor carries a click gesture of its own, and
|
|
252
|
+
* otherwise delivers a press and release. The press travels outwards from the clicked widget
|
|
253
|
+
* through every widget carrying a click gesture with a pressed or released handler, the way GTK
|
|
254
|
+
* hands the same press to each of them, and stops at the first Gtk.Button or indexed child of a
|
|
255
|
+
* list box or flow box, which claims it. A gesture GTK attached itself takes the press only when no
|
|
256
|
+
* widget below it handles one, and stops it there, so an expander or a notebook tab nested in a row
|
|
257
|
+
* opens instead of activating the row. Coordinates are the clicked widget's position in each
|
|
258
|
+
* carrier, so a gesture the container of an indexed child carries reads the child's position rather
|
|
259
|
+
* than the container's center. An indexed child that the press reaches is then activated, or
|
|
260
|
+
* exclusively selected when its container does not activate on a single click. A widget with the
|
|
261
|
+
* label role is never activated, but does consume the click when it carries a gesture of its own.
|
|
262
|
+
*
|
|
263
|
+
* The press also stops at a widget whose click GTK4 implements in C on a gesture it attached
|
|
264
|
+
* itself, reading a GdkEvent that off-screen synthesis cannot produce. Gestures that widget carries
|
|
265
|
+
* of its own still take the press and release, the way GTK hands them the press before its own
|
|
266
|
+
* gesture claims it, and the same outcome is then applied through the public action GTK's own
|
|
267
|
+
* handler invokes: a list, grid, or column-view row is focused and selected, and activated as well
|
|
268
|
+
* on a second press or when its view activates on a single click; an expandable tree expander
|
|
269
|
+
* toggles its expansion, once per press, and only when it is the widget clicked, so a click on its
|
|
270
|
+
* child falls through to the enclosing row; a column header sorts by its column. Sorting goes
|
|
271
|
+
* through Gtk.ColumnView.sortByColumn, so the primary sort column and order match what a pointer
|
|
272
|
+
* produces while previously sorted columns are dropped rather than kept as secondary keys. A
|
|
273
|
+
* column-view cell and the row that carries the column headers stand in the way of the click rather
|
|
274
|
+
* than taking it, so a click on either reaches the row or the view behind it.
|
|
275
|
+
*/
|
|
276
|
+
const click = async (widget: Gtk.Widget): Promise<void> => {
|
|
277
|
+
if (!isSelfClickTarget(widget) && (await tryActivate(widget))) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await deliverClick(widget, 1);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Delivers a two-press click gesture the way {@link click} delivers a single press, without trying
|
|
286
|
+
* activation first. A list box row or flow box child the presses reach, clicked directly or through
|
|
287
|
+
* a descendant, is activated and exclusively selected, as GTK's double-click path does whether or
|
|
288
|
+
* not the container activates on a single click. A widget whose click GTK4 implements itself
|
|
289
|
+
* receives that outcome once per press, so a list, grid, or column-view row is selected and then
|
|
290
|
+
* activated by the second press, a tree expander ends back where it started, and a column header
|
|
291
|
+
* sorts and then inverts its order.
|
|
292
|
+
*/
|
|
293
|
+
const dblClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 2);
|
|
294
|
+
/**
|
|
295
|
+
* Delivers a three-press click gesture the same way a double click is delivered, applying the same
|
|
296
|
+
* outcome to a list box row or flow box child the presses reach, and applying the outcome GTK4
|
|
297
|
+
* implements itself once per press to a row, tree expander, or column header.
|
|
298
|
+
*/
|
|
299
|
+
const tripleClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 3);
|
|
98
300
|
|
|
99
|
-
export {
|
|
301
|
+
export { clickGestures, emitClickPhase, getAuthoredClickGestures, click, dblClick, tripleClick };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { children } from "../traversal.js";
|
|
3
|
+
import { getWidgetTextContent } from "../widget-accessible-properties.js";
|
|
4
|
+
|
|
5
|
+
const columnViewFor = (header: Gtk.Widget): Gtk.ColumnView | null => {
|
|
6
|
+
let current: Gtk.Widget | null = header.getParent();
|
|
7
|
+
|
|
8
|
+
while (current !== null) {
|
|
9
|
+
if (current instanceof Gtk.ColumnView) {
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
current = current.getParent();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const columnsFor = (view: Gtk.ColumnView): Gtk.ColumnViewColumn[] => {
|
|
20
|
+
const model = view.getColumns();
|
|
21
|
+
const columns: Gtk.ColumnViewColumn[] = [];
|
|
22
|
+
|
|
23
|
+
for (let index = 0; index < model.getNItems(); index++) {
|
|
24
|
+
const column = model.getItem(index);
|
|
25
|
+
|
|
26
|
+
if (column instanceof Gtk.ColumnViewColumn) {
|
|
27
|
+
columns.push(column);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return columns;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const headerIndexFor = (header: Gtk.Widget): number => {
|
|
35
|
+
const parent = header.getParent();
|
|
36
|
+
|
|
37
|
+
return parent === null ? -1 : [...children(parent)].indexOf(header);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const titledColumnFor = (columns: Gtk.ColumnViewColumn[], title: string | null): Gtk.ColumnViewColumn | null => {
|
|
41
|
+
const matches = columns.filter((column) => column.getTitle() === title);
|
|
42
|
+
|
|
43
|
+
return matches.length === 1 ? matches[0] ?? null : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const columnFor = (header: Gtk.Widget, view: Gtk.ColumnView): Gtk.ColumnViewColumn | null => {
|
|
47
|
+
const columns = columnsFor(view);
|
|
48
|
+
const picked = columns[headerIndexFor(header)] ?? null;
|
|
49
|
+
const title = getWidgetTextContent(header);
|
|
50
|
+
|
|
51
|
+
if (picked !== null && picked.getTitle() === title) {
|
|
52
|
+
return picked;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return titledColumnFor(columns, title) ?? picked;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const sortableColumnFor = (header: Gtk.Widget, view: Gtk.ColumnView): Gtk.ColumnViewColumn | null => {
|
|
59
|
+
const column = columnFor(header, view);
|
|
60
|
+
|
|
61
|
+
return column !== null && column.getSorter() !== null ? column : null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const invertedSortDirection = (order: Gtk.SortType): Gtk.SortType =>
|
|
65
|
+
order === Gtk.SortType.ASCENDING ? Gtk.SortType.DESCENDING : Gtk.SortType.ASCENDING;
|
|
66
|
+
|
|
67
|
+
const nextSortDirection = (view: Gtk.ColumnView, column: Gtk.ColumnViewColumn): Gtk.SortType => {
|
|
68
|
+
const sorter = view.getSorter();
|
|
69
|
+
|
|
70
|
+
if (!(sorter instanceof Gtk.ColumnViewSorter) || sorter.getPrimarySortColumn() !== column) {
|
|
71
|
+
return Gtk.SortType.ASCENDING;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return invertedSortDirection(sorter.getPrimarySortOrder());
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const applyHeaderClick = (header: Gtk.Widget, nPress: number): void => {
|
|
78
|
+
const view = columnViewFor(header);
|
|
79
|
+
const column = view === null ? null : sortableColumnFor(header, view);
|
|
80
|
+
|
|
81
|
+
if (view === null || column === null) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (let press = 1; press <= nPress; press++) {
|
|
86
|
+
view.sortByColumn(column, nextSortDirection(view, column));
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export { applyHeaderClick };
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
|
|
3
|
+
/** An event controller class that can be constructed without arguments, such as `Gtk.GestureClick`. */
|
|
3
4
|
type ControllerConstructor<T extends Gtk.EventController> = new () => T;
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Returns every controller of the given type attached to a widget, in the order GTK4 reports them.
|
|
8
|
+
*
|
|
9
|
+
* @param widget The widget whose controllers are inspected.
|
|
10
|
+
* @param controllerType The controller class to match.
|
|
11
|
+
* @returns The matching controllers, or an empty array when the widget has none.
|
|
12
|
+
*/
|
|
5
13
|
const queryAllControllers = <T extends Gtk.EventController>(
|
|
6
14
|
widget: Gtk.Widget,
|
|
7
15
|
controllerType: ControllerConstructor<T>,
|
|
@@ -21,11 +29,25 @@ const queryAllControllers = <T extends Gtk.EventController>(
|
|
|
21
29
|
return matches;
|
|
22
30
|
};
|
|
23
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Returns the first controller of the given type attached to a widget.
|
|
34
|
+
*
|
|
35
|
+
* @param widget The widget whose controllers are inspected.
|
|
36
|
+
* @param controllerType The controller class to match.
|
|
37
|
+
* @returns The first matching controller, or null when the widget has none.
|
|
38
|
+
*/
|
|
24
39
|
const queryController = <T extends Gtk.EventController>(
|
|
25
40
|
widget: Gtk.Widget,
|
|
26
41
|
controllerType: ControllerConstructor<T>,
|
|
27
42
|
): T | null => queryAllControllers(widget, controllerType)[0] ?? null;
|
|
28
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Returns every controller of the given type attached to a widget.
|
|
46
|
+
*
|
|
47
|
+
* @param widget The widget whose controllers are inspected.
|
|
48
|
+
* @param controllerType The controller class to match.
|
|
49
|
+
* @throws When the widget has no controller of that type.
|
|
50
|
+
*/
|
|
29
51
|
const getAllControllers = <T extends Gtk.EventController>(
|
|
30
52
|
widget: Gtk.Widget,
|
|
31
53
|
controllerType: ControllerConstructor<T>,
|
|
@@ -39,6 +61,13 @@ const getAllControllers = <T extends Gtk.EventController>(
|
|
|
39
61
|
return controllers;
|
|
40
62
|
};
|
|
41
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Returns the first controller of the given type attached to a widget.
|
|
66
|
+
*
|
|
67
|
+
* @param widget The widget whose controllers are inspected.
|
|
68
|
+
* @param controllerType The controller class to match.
|
|
69
|
+
* @throws When the widget has no controller of that type.
|
|
70
|
+
*/
|
|
42
71
|
const getController = <T extends Gtk.EventController>(
|
|
43
72
|
widget: Gtk.Widget,
|
|
44
73
|
controllerType: ControllerConstructor<T>,
|
|
@@ -14,7 +14,9 @@ const canDisplayDeliverActivation = (window: Gtk.Window): boolean =>
|
|
|
14
14
|
window.getDisplay().getDefaultSeat() !== null;
|
|
15
15
|
|
|
16
16
|
const findWindowActionabilityFailure = (widget: Gtk.Widget, root: Gtk.Window): string | null => {
|
|
17
|
-
|
|
17
|
+
const [isComputed, allocation] = root.computeBounds(root);
|
|
18
|
+
|
|
19
|
+
if (!isComputed || allocation.getWidth() === 0) {
|
|
18
20
|
return WINDOW_NOT_ALLOCATED;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -9,13 +9,17 @@ type DropContent = string | number | boolean | GObject.Value;
|
|
|
9
9
|
|
|
10
10
|
/** Options for a drop: the drop coordinates within the target. */
|
|
11
11
|
type DropOptions = {
|
|
12
|
+
/** Horizontal drop coordinate within the target, in pixels. */
|
|
12
13
|
x?: number;
|
|
14
|
+
/** Vertical drop coordinate within the target, in pixels. */
|
|
13
15
|
y?: number;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/** A drag offset relative to the drag start point. */
|
|
17
19
|
type DragOffset = {
|
|
20
|
+
/** Horizontal distance from the drag start point, in pixels. */
|
|
18
21
|
x: number;
|
|
22
|
+
/** Vertical distance from the drag start point, in pixels. */
|
|
19
23
|
y: number;
|
|
20
24
|
};
|
|
21
25
|
|
|
@@ -24,9 +28,13 @@ type DragOffset = {
|
|
|
24
28
|
* explicit intermediate offsets emitted before the final one.
|
|
25
29
|
*/
|
|
26
30
|
type DragOptions = {
|
|
31
|
+
/** Horizontal coordinate the drag begins at; defaults to 0. */
|
|
27
32
|
startX?: number;
|
|
33
|
+
/** Vertical coordinate the drag begins at; defaults to 0. */
|
|
28
34
|
startY?: number;
|
|
35
|
+
/** Number of evenly interpolated drag updates, the last of which is the requested offset; defaults to 2. */
|
|
29
36
|
steps?: number;
|
|
37
|
+
/** Explicit intermediate offsets emitted before the requested one, used instead of `steps`. */
|
|
30
38
|
offsets?: DragOffset[];
|
|
31
39
|
};
|
|
32
40
|
|
|
@@ -37,8 +45,8 @@ type DragInstancePatch = {
|
|
|
37
45
|
|
|
38
46
|
type SavedDragState = {
|
|
39
47
|
instance: DragInstancePatch;
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
hasOwnStartPoint: boolean;
|
|
49
|
+
hasOwnOffset: boolean;
|
|
42
50
|
previousStartPoint: DragInstancePatch["getStartPoint"];
|
|
43
51
|
previousOffset: DragInstancePatch["getOffset"];
|
|
44
52
|
};
|
|
@@ -65,32 +73,56 @@ const buildDropValue = (content: DropContent): GObject.Value => {
|
|
|
65
73
|
});
|
|
66
74
|
};
|
|
67
75
|
|
|
76
|
+
/** Emits `enter` at the widget's origin on its motion controllers, adding one when it has none. */
|
|
68
77
|
const hover = (widget: Gtk.Widget): Promise<void> =>
|
|
69
78
|
dispatchOnOrCreateControllers(widget, Gtk.EventControllerMotion, (controller) => {
|
|
70
79
|
controller.emit("enter", 0, 0);
|
|
71
80
|
});
|
|
72
81
|
|
|
82
|
+
/** Emits `leave` on the widget's motion controllers, adding one when it has none. */
|
|
73
83
|
const unhover = (widget: Gtk.Widget): Promise<void> =>
|
|
74
84
|
dispatchOnOrCreateControllers(widget, Gtk.EventControllerMotion, (controller) => {
|
|
75
85
|
controller.emit("leave");
|
|
76
86
|
});
|
|
77
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Emits `angle-changed` on the widget's rotate gestures.
|
|
90
|
+
*
|
|
91
|
+
* @param angle Current angle, in radians.
|
|
92
|
+
* @param deltaAngle Difference from the angle the gesture started at; defaults to `angle`.
|
|
93
|
+
* @throws When the widget has no Gtk.GestureRotate.
|
|
94
|
+
*/
|
|
78
95
|
const rotate = (widget: Gtk.Widget, angle: number, deltaAngle: number = angle): Promise<void> =>
|
|
79
96
|
dispatchOnControllers(widget, Gtk.GestureRotate, (controller) => {
|
|
80
97
|
controller.emit("angle-changed", angle, deltaAngle);
|
|
81
98
|
},
|
|
82
99
|
);
|
|
83
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Emits `scale-changed` with the given scale delta on the widget's zoom gestures.
|
|
103
|
+
*
|
|
104
|
+
* @throws When the widget has no Gtk.GestureZoom.
|
|
105
|
+
*/
|
|
84
106
|
const zoom = (widget: Gtk.Widget, scale: number): Promise<void> =>
|
|
85
107
|
dispatchOnControllers(widget, Gtk.GestureZoom, (controller) => {
|
|
86
108
|
controller.emit("scale-changed", scale);
|
|
87
109
|
});
|
|
88
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Emits `swipe` with the given per-axis velocity, in pixels per second, on the widget's swipe gestures.
|
|
113
|
+
*
|
|
114
|
+
* @throws When the widget has no Gtk.GestureSwipe.
|
|
115
|
+
*/
|
|
89
116
|
const swipe = (widget: Gtk.Widget, velocityX: number, velocityY: number): Promise<void> =>
|
|
90
117
|
dispatchOnControllers(widget, Gtk.GestureSwipe, (controller) => {
|
|
91
118
|
controller.emit("swipe", velocityX, velocityY);
|
|
92
119
|
});
|
|
93
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Emits `pressed` at the given point in widget coordinates on the widget's long-press gestures.
|
|
123
|
+
*
|
|
124
|
+
* @throws When the widget has no Gtk.GestureLongPress.
|
|
125
|
+
*/
|
|
94
126
|
const longPress = (widget: Gtk.Widget, x = 0, y = 0): Promise<void> =>
|
|
95
127
|
dispatchOnControllers(widget, Gtk.GestureLongPress, (controller) => {
|
|
96
128
|
controller.emit("pressed", x, y);
|
|
@@ -99,13 +131,13 @@ const longPress = (widget: Gtk.Widget, x = 0, y = 0): Promise<void> =>
|
|
|
99
131
|
const restoreDragState = (saved: SavedDragState): void => {
|
|
100
132
|
const { instance } = saved;
|
|
101
133
|
|
|
102
|
-
if (saved.
|
|
134
|
+
if (saved.hasOwnStartPoint) {
|
|
103
135
|
instance.getStartPoint = saved.previousStartPoint;
|
|
104
136
|
} else {
|
|
105
137
|
delete instance.getStartPoint;
|
|
106
138
|
}
|
|
107
139
|
|
|
108
|
-
if (saved.
|
|
140
|
+
if (saved.hasOwnOffset) {
|
|
109
141
|
instance.getOffset = saved.previousOffset;
|
|
110
142
|
} else {
|
|
111
143
|
delete instance.getOffset;
|
|
@@ -117,8 +149,8 @@ const patchDragState = (controller: Gtk.GestureDrag, start: DragOffset, offset:
|
|
|
117
149
|
|
|
118
150
|
const saved: SavedDragState = {
|
|
119
151
|
instance,
|
|
120
|
-
|
|
121
|
-
|
|
152
|
+
hasOwnStartPoint: Object.hasOwn(instance, "getStartPoint"),
|
|
153
|
+
hasOwnOffset: Object.hasOwn(instance, "getOffset"),
|
|
122
154
|
previousStartPoint: instance.getStartPoint,
|
|
123
155
|
previousOffset: instance.getOffset,
|
|
124
156
|
};
|
|
@@ -177,6 +209,14 @@ const resolveDragUpdates = (dx: number, dy: number, options: DragOptions): DragO
|
|
|
177
209
|
return updates;
|
|
178
210
|
};
|
|
179
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Runs a `drag-begin`, `drag-update`, `drag-end` sequence ending at the offset `dx`, `dy` on the
|
|
214
|
+
* widget's drag gestures, overriding each gesture's start point and offset for the duration so
|
|
215
|
+
* handlers read the simulated values back.
|
|
216
|
+
*
|
|
217
|
+
* @throws When the widget is a Gtk.Range, whose slider reads pointer coordinates from the display,
|
|
218
|
+
* or when it has no Gtk.GestureDrag.
|
|
219
|
+
*/
|
|
180
220
|
const drag = async (widget: Gtk.Widget, dx: number, dy: number, options: DragOptions = {}): Promise<void> => {
|
|
181
221
|
if (widget instanceof Gtk.Range) {
|
|
182
222
|
throw new TypeError(
|
|
@@ -202,11 +242,22 @@ const emitDrop = (target: Gtk.Widget, content: DropContent, options: DropOptions
|
|
|
202
242
|
}
|
|
203
243
|
};
|
|
204
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Emits `drop` with the given content on every drop target attached to the widget.
|
|
247
|
+
*
|
|
248
|
+
* @throws When the widget has no Gtk.DropTarget.
|
|
249
|
+
*/
|
|
205
250
|
const drop = (widget: Gtk.Widget, content: DropContent, options: DropOptions = {}): Promise<void> =>
|
|
206
251
|
wrapEvent(widget, () => {
|
|
207
252
|
emitDrop(widget, content, options);
|
|
208
253
|
});
|
|
209
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Emits `drop` with the given content on the target's drop targets, after checking that the source
|
|
257
|
+
* carries a drag source.
|
|
258
|
+
*
|
|
259
|
+
* @throws When the source has no Gtk.DragSource, or the target no Gtk.DropTarget.
|
|
260
|
+
*/
|
|
210
261
|
const dragAndDrop = async (
|
|
211
262
|
source: Gtk.Widget,
|
|
212
263
|
target: Gtk.Widget,
|