diff --git a/timeline-chart/src/layer/time-graph-navigator.ts b/timeline-chart/src/layer/time-graph-navigator.ts index da84518..04b5d00 100644 --- a/timeline-chart/src/layer/time-graph-navigator.ts +++ b/timeline-chart/src/layer/time-graph-navigator.ts @@ -9,12 +9,15 @@ import { BIMath } from "../bigint-utils"; export class TimeGraphNavigator extends TimeGraphLayer { protected navigatorHandle: TimeGraphNavigatorHandle; + protected navigatorBackground: TimeGraphNavigatorBackground; protected selectionRange?: TimeGraphRectangle; private _updateHandler: { (): void; (viewRange: TimelineChart.TimeGraphRange): void; (selectionRange: TimelineChart.TimeGraphRange): void; (viewRange: TimelineChart.TimeGraphRange): void; (selectionRange: TimelineChart.TimeGraphRange): void; }; afterAddToContainer() { this._updateHandler = (): void => this.update(); this.unitController.onViewRangeChanged(this._updateHandler); + this.navigatorBackground = new TimeGraphNavigatorBackground(this.unitController, this.stateController); + this.addChild(this.navigatorBackground); this.navigatorHandle = new TimeGraphNavigatorHandle(this.unitController, this.stateController); this.addChild(this.navigatorHandle); this.unitController.onSelectionRangeChange(this._updateHandler); @@ -66,11 +69,21 @@ export class TimeGraphNavigatorHandle extends TimeGraphComponent { constructor(protected unitController: TimeGraphUnitController, protected stateController: TimeGraphStateController) { super('navigator_handle'); - this.addEvent('mousedown', event => { + const moveStart: TimeGraphInteractionHandler = event => { this.mouseStartX = event.data.global.x; this.oldViewStart = this.unitController.viewRange.start; this.mouseIsDown = true; + this.stateController.snapped = false; + } + const moveEnd = () => { + this.mouseIsDown = false; + } + this.addEvent('mouseover', event => { + if (this.stateController.snapped) { + moveStart(event); + } }, this._displayObject); + this.addEvent('mousedown', moveStart, this._displayObject); this.addEvent('mousemove', event => { if (this.mouseIsDown) { const delta = event.data.global.x - this.mouseStartX; @@ -83,11 +96,9 @@ export class TimeGraphNavigatorHandle extends TimeGraphComponent { } } }, this._displayObject); - const moveEnd: TimeGraphInteractionHandler = event => { - this.mouseIsDown = false; - } this.addEvent('mouseup', moveEnd, this._displayObject); this.addEvent('mouseupoutside', moveEnd, this._displayObject); + document.addEventListener('snap-x-end', moveEnd); } render(): void { @@ -104,4 +115,55 @@ export class TimeGraphNavigatorHandle extends TimeGraphComponent { color: 0x777769 }) } +} + +export class TimeGraphNavigatorBackground extends TimeGraphComponent { + + protected snapEvent: CustomEvent; + protected snapEventString: string; + + constructor(protected unitController: TimeGraphUnitController, protected stateController: TimeGraphStateController) { + super("navigator_background"); + this.addEvent("mousedown", event => { + let x = event.data.getLocalPosition(this._displayObject).x; + let middle = BIMath.round((x / this.stateController.canvasDisplayWidth) * Number(this.unitController.absoluteRange)); + // We have horizontal offset at point of click, now we need coord for start of handler. + let hVL = this.unitController.viewRangeLength / BigInt(2); + let start0 = middle - hVL; + let max = this.unitController.absoluteRange - this.unitController.viewRangeLength; + let min = BigInt(0); + // Clamp it. + const start = BIMath.clamp(start0, min, max); + this.unitController.viewRange = { + start, + end: start + this.unitController.viewRangeLength + } + // Set snapped state + this.toggleSnappedState(true); + }, this._displayObject); + // Custom event lets handler know 'mouseup' triggers. + this.snapEvent = new CustomEvent(this.snapEventString = 'snap-x-end'); + const endSnap = () => { + this.toggleSnappedState(false); + document.dispatchEvent(this.snapEvent); + } + this.addEvent('mouseup', endSnap, this._displayObject); + this.addEvent('mouseupoutside', endSnap, this._displayObject); + } + + protected toggleSnappedState = (bool: boolean) => { + this.stateController.snapped = bool; + } + + render(): void { + this.rect({ + height: 20, + position: { + x: 0, + y: 0 + }, + width: this.stateController.canvasDisplayWidth, + opacity: 0 + }); + } } \ No newline at end of file diff --git a/timeline-chart/src/layer/time-graph-vertical-scrollbar.ts b/timeline-chart/src/layer/time-graph-vertical-scrollbar.ts index 1e940d4..6be11a6 100644 --- a/timeline-chart/src/layer/time-graph-vertical-scrollbar.ts +++ b/timeline-chart/src/layer/time-graph-vertical-scrollbar.ts @@ -7,6 +7,7 @@ import { TimeGraphRowController } from "../time-graph-row-controller"; export class TimeGraphVerticalScrollbar extends TimeGraphChartLayer { protected navigatorHandle: TimeGraphVerticalScrollbarHandle; + protected navigatorBackground: TimeGraphVerticalScrollbarBackground; protected selectionRange?: TimeGraphRectangle; protected factor: number; @@ -18,11 +19,14 @@ export class TimeGraphVerticalScrollbar extends TimeGraphChartLayer { protected afterAddToContainer() { this.updateFactor(); this.navigatorHandle = new TimeGraphVerticalScrollbarHandle(this.rowController, this.stateController, this.factor); + this.navigatorBackground = new TimeGraphVerticalScrollbarBackground(this.rowController, this.stateController, this.factor); + this.addChild(this.navigatorBackground); this.addChild(this.navigatorHandle); this.rowController.onVerticalOffsetChangedHandler(() => this.update()); this.rowController.onTotalHeightChangedHandler(() => { this.updateFactor(); this.navigatorHandle.updateFactor(this.factor); + this.navigatorBackground.updateFactor(this.factor); this.update() }); } @@ -38,6 +42,8 @@ export class TimeGraphVerticalScrollbar extends TimeGraphChartLayer { update() { this.navigatorHandle.clear(); this.navigatorHandle.render(); + this.navigatorBackground.clear(); + this.navigatorBackground.render(); } } @@ -52,11 +58,21 @@ export class TimeGraphVerticalScrollbarHandle extends TimeGraphComponent { constructor(protected rowController: TimeGraphRowController, protected stateController: TimeGraphStateController, protected factor: number) { super('vscroll_handle'); - this.addEvent('mousedown', event => { + const moveStart: TimeGraphInteractionHandler = event => { this.mouseStartY = event.data.global.y; - this.oldVerticalOffset = this.rowController.verticalOffset + this.oldVerticalOffset = this.rowController.verticalOffset; this.mouseIsDown = true; + this.stateController.snapped = false; + }; + const moveEnd = () => { + this.mouseIsDown = false; + } + this.addEvent('mouseover', (event) => { + if (this.stateController.snapped) { + moveStart(event); + } }, this._displayObject); + this.addEvent('mousedown', moveStart, this._displayObject); this.addEvent('mousemove', event => { if (this.mouseIsDown) { const delta = (event.data.global.y - this.mouseStartY) / this.factor; @@ -64,11 +80,9 @@ export class TimeGraphVerticalScrollbarHandle extends TimeGraphComponent { this.rowController.verticalOffset = Math.max(0, Math.min(this.rowController.totalHeight - this.stateController.canvasDisplayHeight, verticalOffset)); } }, this._displayObject); - const moveEnd: TimeGraphInteractionHandler = event => { - this.mouseIsDown = false; - } this.addEvent('mouseup', moveEnd, this._displayObject); this.addEvent('mouseupoutside', moveEnd, this._displayObject); + document.addEventListener('snap-y-end', moveEnd); } updateFactor(factor: number) { @@ -85,4 +99,51 @@ export class TimeGraphVerticalScrollbarHandle extends TimeGraphComponent { color: 0x777769 }) } +} + +export class TimeGraphVerticalScrollbarBackground extends TimeGraphComponent { + + protected snapEvent: CustomEvent; + protected snapEventString: string; + + constructor(protected rowController: TimeGraphRowController, protected stateController: TimeGraphStateController, protected factor: number) { + super("vscroll_background"); + this.addEvent("mousedown", event => { + let yPosition = event.data.getLocalPosition(this._displayObject).y; + let vOffset = (yPosition/this.stateController.canvasDisplayHeight) * this.rowController.totalHeight; + // We have vertical offset at point of click, but need to make it the center of scrollbar. + let scrollBarHeight = (this.rowController.totalHeight * this.factor); + vOffset = vOffset - (scrollBarHeight / 2); + // Clamp it + let vOffsetClamped = Math.max(0, Math.min(this.rowController.totalHeight - this.stateController.canvasDisplayHeight, vOffset)); + this.rowController.verticalOffset = vOffsetClamped; + // Set snapped state + this.toggleSnappedState(true); + }, this._displayObject); + // Emit custom event to let vertical handler know when 'mousedown' is released. + this.snapEvent = new CustomEvent(this.snapEventString = 'snap-y-end'); + const endSnap = () => { + this.toggleSnappedState(false); + document.dispatchEvent(this.snapEvent); + } + this.addEvent('mouseup', endSnap, this._displayObject); + this.addEvent('mouseupoutside', endSnap, this._displayObject); + } + + updateFactor(factor: number) { + this.factor = factor; + } + + protected toggleSnappedState = (bool: boolean) => { + this.stateController.snapped = bool; + } + + render(): void { + this.rect({ + height: this.stateController.canvasDisplayHeight, + position: { x: 0, y: 0 }, + width: 10, + opacity: 0 + }); + } } \ No newline at end of file diff --git a/timeline-chart/src/time-graph-state-controller.ts b/timeline-chart/src/time-graph-state-controller.ts index c7e9762..2b714d4 100644 --- a/timeline-chart/src/time-graph-state-controller.ts +++ b/timeline-chart/src/time-graph-state-controller.ts @@ -5,6 +5,8 @@ export class TimeGraphStateController { x: number; y: number; }; + + snapped: boolean; protected ratio: number; @@ -32,6 +34,7 @@ export class TimeGraphStateController { this.zoomChangedHandlers = []; this.positionChangedHandlers = []; this.canvasDisplayWidthChangedHandlers = []; + this.snapped = false; } protected handleZoomChange() { @@ -102,4 +105,5 @@ export class TimeGraphStateController { this._positionOffset = value; this.handlePositionChange(); } + } \ No newline at end of file