Skip to content

Commit 831fa4a

Browse files
committed
Support separate horizontal and vertical scroll hosts for draw-selection anchoring and auto-scroll
- Split single scrollHost into horizontalScrollHost and verticalScrollHost so the selection rectangle stays anchored to content during both horizontal and vertical scrolling - Auto-scroll scrolls the horizontal host for X and the vertical host for Y independently - Remove unused PermMatrix component and its tests. Signed-off-by: Mike Lischke <mike@lischke-online.de>
1 parent c2d05fc commit 831fa4a

5 files changed

Lines changed: 98 additions & 196 deletions

File tree

src/components/ui/Arrangement/ArrangementViewer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ export class ArrangementViewer extends UIComponent<IArrangementViewerProps, IArr
9494
const { autoFollowIsOn, viewerZoom } = this.state;
9595

9696
services.selectionManager.setEventContainer(this.arrangementViewerRef.current!);
97-
services.selectionManager.setScrollHost(this.viewerRef.current!);
97+
98+
const verticalHost = this.arrangementViewerRef.current?.parentElement;
99+
if (verticalHost) {
100+
services.selectionManager.setScrollHosts(this.viewerRef.current!, verticalHost);
101+
}
98102

99103
requisitions.register("settingsChanged", this.handleSettingsChanged);
100104
requisitions.register("trackViewModeToggled", this.handleTrackViewModeToggled);

src/ui/PermMatrix.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/ui/SelectionManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,14 @@ export class SelectionManager {
102102
}
103103

104104
/**
105-
* Sets the scroll host element for the selection view.
105+
* Sets the scroll host elements for the selection view.
106106
* Must be called after {@link setEventContainer}.
107107
*
108-
* @param host The scrollable container element (typically `#trackViewerHost`).
108+
* @param horizontal The horizontally-scrollable container (typically `#trackViewerHost`).
109+
* @param vertical The vertically-scrollable container.
109110
*/
110-
public setScrollHost(host: HTMLElement): void {
111-
this.view?.setScrollHost(host);
111+
public setScrollHosts(horizontal: HTMLElement, vertical: HTMLElement): void {
112+
this.view?.setScrollHosts(horizontal, vertical);
112113
}
113114

114115
public registerHitTester(tester: ISelectionHitTester): void {

src/ui/SelectionView.ts

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ const formElementNames = new Set(["BUTTON", "INPUT", "SELECT", "TEXTAREA"]);
2626
export class SelectionView {
2727
private rectElement?: HTMLDivElement;
2828
private captureElement?: HTMLElement;
29-
private scrollHost?: HTMLElement;
29+
private horizontalScrollHost?: HTMLElement;
30+
private verticalScrollHost?: HTMLElement;
3031

3132
private dragPending = false;
3233
private isDragging = false;
3334
private startX = 0;
3435
private startY = 0;
3536
private startScrollLeft = 0;
3637
private startScrollTop = 0;
38+
private startVerticalScrollTop = 0;
3739
private lastPointerX = 0;
3840
private lastPointerY = 0;
3941
private autoScrollTimer?: ReturnType<typeof setInterval>;
@@ -63,13 +65,15 @@ export class SelectionView {
6365
}
6466

6567
/**
66-
* Sets the scroll host element whose scroll position is tracked during drag selection
68+
* Sets the scroll host elements whose scroll positions are tracked during drag selection
6769
* so the selection rectangle stays anchored to the content. Also enables edge auto-scroll.
6870
*
69-
* @param host The scrollable container element (typically `#trackViewerHost`).
71+
* @param horizontal The horizontally-scrollable container (typically `#trackViewerHost`).
72+
* @param vertical The vertically-scrollable container (typically the arrangement viewer parent).
7073
*/
71-
public setScrollHost(host: HTMLElement): void {
72-
this.scrollHost = host;
74+
public setScrollHosts(horizontal: HTMLElement, vertical: HTMLElement): void {
75+
this.horizontalScrollHost = horizontal;
76+
this.verticalScrollHost = vertical;
7377
}
7478

7579
private handlePointerDown = (event: PointerEvent): void => {
@@ -90,10 +94,15 @@ export class SelectionView {
9094
this.lastPointerX = event.clientX;
9195
this.lastPointerY = event.clientY;
9296

93-
if (this.scrollHost) {
94-
this.startScrollLeft = this.scrollHost.scrollLeft;
95-
this.startScrollTop = this.scrollHost.scrollTop;
96-
this.scrollHost.addEventListener("scroll", this.handleScroll, { passive: true });
97+
if (this.horizontalScrollHost) {
98+
this.startScrollLeft = this.horizontalScrollHost.scrollLeft;
99+
this.startScrollTop = this.horizontalScrollHost.scrollTop;
100+
this.horizontalScrollHost.addEventListener("scroll", this.handleScroll, { passive: true });
101+
}
102+
103+
if (this.verticalScrollHost && this.verticalScrollHost !== this.horizontalScrollHost) {
104+
this.startVerticalScrollTop = this.verticalScrollHost.scrollTop;
105+
this.verticalScrollHost.addEventListener("scroll", this.handleScroll, { passive: true });
97106
}
98107

99108
this.createRectElement(event.clientX, event.clientY);
@@ -148,21 +157,44 @@ export class SelectionView {
148157
};
149158

150159
/**
151-
* Adjusts the selection start point when the scroll host scrolls during a drag,
160+
* Adjusts the selection start point when any scroll host scrolls during a drag,
152161
* keeping the anchor pinned to the content rather than the viewport.
162+
*
163+
* @param event The scroll event from one of the registered scroll hosts.
153164
*/
154-
private handleScroll = (): void => {
155-
if (!this.scrollHost || !this.rectElement) {
165+
private handleScroll = (event: Event): void => {
166+
if (!this.rectElement) {
156167
return;
157168
}
158169

159-
const deltaX = this.scrollHost.scrollLeft - this.startScrollLeft;
160-
const deltaY = this.scrollHost.scrollTop - this.startScrollTop;
170+
const target = event.target;
171+
let deltaX = 0;
172+
let deltaY = 0;
173+
let handled = false;
174+
175+
const hHost = this.horizontalScrollHost;
176+
if (hHost && target === hHost) {
177+
deltaX = hHost.scrollLeft - this.startScrollLeft;
178+
deltaY = hHost.scrollTop - this.startScrollTop;
179+
this.startScrollLeft = hHost.scrollLeft;
180+
this.startScrollTop = hHost.scrollTop;
181+
handled = true;
182+
}
183+
184+
const vHost = this.verticalScrollHost;
185+
if (vHost && target === vHost) {
186+
const verticalDelta = vHost.scrollTop - this.startVerticalScrollTop;
187+
deltaY += verticalDelta;
188+
this.startVerticalScrollTop = vHost.scrollTop;
189+
handled = true;
190+
}
191+
192+
if (!handled) {
193+
return;
194+
}
161195

162196
this.startX -= deltaX;
163197
this.startY -= deltaY;
164-
this.startScrollLeft = this.scrollHost.scrollLeft;
165-
this.startScrollTop = this.scrollHost.scrollTop;
166198

167199
this.updateRectElement(this.lastPointerX, this.lastPointerY);
168200

@@ -203,12 +235,17 @@ export class SelectionView {
203235
this.stopAutoScroll();
204236
this.removeRectElement();
205237

206-
if (this.scrollHost) {
207-
this.scrollHost.removeEventListener("scroll", this.handleScroll);
238+
if (this.horizontalScrollHost) {
239+
this.horizontalScrollHost.removeEventListener("scroll", this.handleScroll);
208240
this.startScrollLeft = 0;
209241
this.startScrollTop = 0;
210242
}
211243

244+
if (this.verticalScrollHost && this.verticalScrollHost !== this.horizontalScrollHost) {
245+
this.verticalScrollHost.removeEventListener("scroll", this.handleScroll);
246+
this.startVerticalScrollTop = 0;
247+
}
248+
212249
if (this.captureElement) {
213250
this.captureElement.removeEventListener("pointermove", this.handlePointerMove);
214251
this.captureElement.removeEventListener("pointerup", this.handlePointerUp);
@@ -225,38 +262,56 @@ export class SelectionView {
225262
* @param clientY The current pointer Y position in viewport coordinates.
226263
*/
227264
private updateAutoScroll(clientX: number, clientY: number): void {
228-
if (!this.scrollHost) {
265+
if (!this.horizontalScrollHost && !this.verticalScrollHost) {
229266
return;
230267
}
231268

232-
const hostRect = this.scrollHost.getBoundingClientRect();
233269
const edgeThreshold = 10;
234270
let scrollDX = 0;
235271
let scrollDY = 0;
236272

237-
const distLeft = clientX - hostRect.left;
238-
const distRight = hostRect.right - clientX;
239-
const distTop = clientY - hostRect.top;
240-
const distBottom = hostRect.bottom - clientY;
273+
// Horizontal auto-scroll on the horizontal host.
274+
if (this.horizontalScrollHost) {
275+
const hostRect = this.horizontalScrollHost.getBoundingClientRect();
276+
const distLeft = clientX - hostRect.left;
277+
const distRight = hostRect.right - clientX;
241278

242-
if (distLeft < edgeThreshold) {
243-
scrollDX = -Math.max(1, Math.ceil((edgeThreshold - distLeft) / 2));
244-
} else if (distRight < edgeThreshold) {
245-
scrollDX = Math.max(1, Math.ceil((edgeThreshold - distRight) / 2));
279+
if (distLeft < edgeThreshold) {
280+
scrollDX = -Math.max(1, Math.ceil((edgeThreshold - distLeft) / 2));
281+
} else if (distRight < edgeThreshold) {
282+
scrollDX = Math.max(1, Math.ceil((edgeThreshold - distRight) / 2));
283+
}
246284
}
247285

248-
if (distTop < edgeThreshold) {
249-
scrollDY = -Math.max(1, Math.ceil((edgeThreshold - distTop) / 2));
250-
} else if (distBottom < edgeThreshold) {
251-
scrollDY = Math.max(1, Math.ceil((edgeThreshold - distBottom) / 2));
286+
// Vertical auto-scroll on the vertical host.
287+
const verticalHost = this.verticalScrollHost ?? this.horizontalScrollHost;
288+
if (verticalHost) {
289+
const hostRect = verticalHost.getBoundingClientRect();
290+
const distTop = clientY - hostRect.top;
291+
const distBottom = hostRect.bottom - clientY;
292+
293+
if (distTop < edgeThreshold) {
294+
scrollDY = -Math.max(1, Math.ceil((edgeThreshold - distTop) / 2));
295+
} else if (distBottom < edgeThreshold) {
296+
scrollDY = Math.max(1, Math.ceil((edgeThreshold - distBottom) / 2));
297+
}
252298
}
253299

254300
this.autoScrollDX = scrollDX;
255301
this.autoScrollDY = scrollDY;
256302

257303
if (scrollDX !== 0 || scrollDY !== 0) {
258304
this.autoScrollTimer ??= setInterval(() => {
259-
this.scrollHost!.scrollBy(this.autoScrollDX, this.autoScrollDY);
305+
if (this.autoScrollDX !== 0 && this.horizontalScrollHost) {
306+
this.horizontalScrollHost.scrollBy(this.autoScrollDX, 0);
307+
}
308+
309+
if (this.autoScrollDY !== 0) {
310+
const vh = this.verticalScrollHost ?? this.horizontalScrollHost;
311+
if (vh) {
312+
vh.scrollBy(0, this.autoScrollDY);
313+
}
314+
}
260315
}, 16);
261316
} else {
262317
this.stopAutoScroll();

tests/ui/PermMatrix.spec.tsx

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)