From 484fbfcbbd84b80bbda219797d8aa1e5df895e55 Mon Sep 17 00:00:00 2001 From: Patrick Tasse Date: Thu, 8 Apr 2021 15:25:29 -0400 Subject: [PATCH] Support horizontal panning with middle button or Ctrl+left button Allow panning with the mouse with middle button drag or Ctrl+left button drag. Change the cursor to 'grabbing' while panning is ongoing. Change the cursor to 'grabbing' when only Ctrl is pressed to indicate that left button can be used to pan. Restore the cursor to 'default' when panning is ended by releasing the mouse button that initiated it, or when the conditions to initiate Ctrl+left button panning are no longer met. Prevent time selection with any other button than the left button or when the Ctrl or Alt key is pressed. Signed-off-by: Patrick Tasse --- .../src/layer/time-graph-chart-cursors.ts | 26 +++++--- timeline-chart/src/layer/time-graph-chart.ts | 62 ++++++++++++++++++- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/timeline-chart/src/layer/time-graph-chart-cursors.ts b/timeline-chart/src/layer/time-graph-chart-cursors.ts index 0fd189b..9be8be6 100644 --- a/timeline-chart/src/layer/time-graph-chart-cursors.ts +++ b/timeline-chart/src/layer/time-graph-chart-cursors.ts @@ -8,7 +8,7 @@ import { TimeGraphRowController } from "../time-graph-row-controller"; import { TimeGraphChart } from "./time-graph-chart"; export class TimeGraphChartCursors extends TimeGraphChartLayer { - protected mouseIsDown: boolean; + protected mouseSelecting: boolean = false; protected shiftKeyDown: boolean; protected firstCursor?: TimeGraphCursor; protected secondCursor?: TimeGraphCursor; @@ -22,7 +22,7 @@ export class TimeGraphChartCursors extends TimeGraphChartLayer { } protected afterAddToContainer() { - this.mouseIsDown = false; + this.mouseSelecting = false; this.shiftKeyDown = false this.stage.interactive = true; @@ -48,7 +48,12 @@ export class TimeGraphChartCursors extends TimeGraphChartLayer { this.onCanvasEvent('keyup', keyUpHandler); this.stage.on('mousedown', (event: PIXI.InteractionEvent) => { - this.mouseIsDown = true; + // if only left button is pressed with or without Shift key + if (event.data.button !== 0 || event.data.buttons !== 1 || + event.data.originalEvent.ctrlKey || event.data.originalEvent.altKey) { + return; + } + this.mouseSelecting = true; const mouseX = event.data.global.x; const xpos = this.unitController.viewRange.start + (mouseX / this.stateController.zoomFactor); this.chartLayer.selectRowElement(undefined); @@ -66,7 +71,7 @@ export class TimeGraphChartCursors extends TimeGraphChartLayer { } }); this.stage.on('mousemove', (event: PIXI.InteractionEvent) => { - if (this.mouseIsDown && this.unitController.selectionRange) { + if (this.mouseSelecting && this.unitController.selectionRange) { const mouseX = event.data.global.x; const xStartPos = this.unitController.selectionRange.start; const xEndPos = this.unitController.viewRange.start + (mouseX / this.stateController.zoomFactor); @@ -76,12 +81,13 @@ export class TimeGraphChartCursors extends TimeGraphChartLayer { } } }); - this.stage.on('mouseup', (event: PIXI.InteractionEvent) => { - this.mouseIsDown = false; - }); - this.stage.on('mouseupoutside', (event: PIXI.InteractionEvent) => { - this.mouseIsDown = false; - }); + const mouseUpHandler = (event: PIXI.InteractionEvent) => { + if (this.mouseSelecting && event.data.button === 0) { + this.mouseSelecting = false; + } + }; + this.stage.on('mouseup', mouseUpHandler); + this.stage.on('mouseupoutside', mouseUpHandler); this.unitController.onViewRangeChanged(() => this.update()); this.unitController.onSelectionRangeChange(() => this.update()); this.update(); diff --git a/timeline-chart/src/layer/time-graph-chart.ts b/timeline-chart/src/layer/time-graph-chart.ts index 3e55819..b2a3dfb 100644 --- a/timeline-chart/src/layer/time-graph-chart.ts +++ b/timeline-chart/src/layer/time-graph-chart.ts @@ -48,6 +48,11 @@ export class TimeGraphChart extends TimeGraphChartLayer { protected isNavigating: boolean; + protected mousePanning: boolean = false; + protected mouseButtons: number = 0; + protected mouseDownButton: number; + protected mouseStartX: number; + constructor(id: string, protected providers: TimeGraphChartProviders, protected rowController: TimeGraphRowController) { @@ -107,9 +112,14 @@ export class TimeGraphChart extends TimeGraphChartLayer { }; const keyDownHandler = (event: KeyboardEvent) => { - let keyPressed = event.key; - + const keyPressed = event.key; if (triggerKeyEvent) { + if (keyPressed === 'Control' && this.mouseButtons === 0 && !event.shiftKey && !event.altKey) { + this.stage.cursor = 'grabbing'; + } else if (this.stage.cursor === 'grabbing' && !this.mousePanning && + (keyPressed === 'Shift' || keyPressed === 'Alt')) { + this.stage.cursor = 'default'; + } if (keyBoardNavs['zoomin'].indexOf(keyPressed) >= 0) { const zoomPosition = (mousePositionX / this.stateController.zoomFactor); adjustZoom(zoomPosition, true); @@ -127,6 +137,14 @@ export class TimeGraphChart extends TimeGraphChartLayer { event.preventDefault(); } }; + const keyUpHandler = (event: KeyboardEvent) => { + const keyPressed = event.key; + if (triggerKeyEvent) { + if (this.stage.cursor === 'grabbing' && !this.mousePanning && keyPressed === 'Control' ) { + this.stage.cursor = 'default'; + } + } + }; this.stage.addListener('mouseover', (event: MouseEvent) => { triggerKeyEvent = true; @@ -134,8 +152,47 @@ export class TimeGraphChart extends TimeGraphChartLayer { this.stage.addListener('mouseout', (event: MouseEvent) => { triggerKeyEvent = false; + if (this.stage.cursor === 'grabbing' && !this.mousePanning) { + this.stage.cursor = 'default'; + } }); + this.stage.on('mousedown', (event: PIXI.InteractionEvent) => { + this.mouseButtons = event.data.buttons; + // if only middle button or only Ctrl+left button is pressed + if ((event.data.button !== 1 || event.data.buttons !== 4) && + (event.data.button !== 0 || event.data.buttons !== 1 || + !event.data.originalEvent.ctrlKey || + event.data.originalEvent.shiftKey || + event.data.originalEvent.altKey || + this.stage.cursor !== 'grabbing')) { + return; + } + this.mousePanning = true; + this.mouseDownButton = event.data.button; + this.mouseStartX = event.data.global.x; + this.stage.cursor = 'grabbing'; + }); + this.stage.on('mousemove', (event: PIXI.InteractionEvent) => { + if (this.mousePanning) { + const horizontalDelta = this.mouseStartX - event.data.global.x; + moveHorizontally(horizontalDelta); + this.mouseStartX = event.data.global.x; + } + }); + const mouseUpHandler = (event: PIXI.InteractionEvent) => { + this.mouseButtons = event.data.buttons; + if (event.data.button === this.mouseDownButton && this.mousePanning) { + this.mousePanning = false; + const orig = event.data.originalEvent; + if (!orig.ctrlKey || orig.shiftKey || orig.altKey) { + this.stage.cursor = 'default'; + } + } + }; + this.stage.on('mouseup', mouseUpHandler); + this.stage.on('mouseupoutside', mouseUpHandler); + const mouseWheelHandler = (ev: WheelEvent) => { if (ev.ctrlKey) { const zoomPosition = (ev.offsetX / this.stateController.zoomFactor); @@ -156,6 +213,7 @@ export class TimeGraphChart extends TimeGraphChartLayer { this.onCanvasEvent('mousemove', mouseMoveHandler); this.onCanvasEvent('keydown', keyDownHandler); + this.onCanvasEvent('keyup', keyUpHandler); this.onCanvasEvent('mousewheel', mouseWheelHandler); this.onCanvasEvent('wheel', mouseWheelHandler);