Merge "Allow 3 strategies for selecting the overview timeline."
diff --git a/ui/src/frontend/drag/border_drag_strategy.ts b/ui/src/frontend/drag/border_drag_strategy.ts
new file mode 100644
index 0000000..b91169d
--- /dev/null
+++ b/ui/src/frontend/drag/border_drag_strategy.ts
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import {TimeScale} from '../time_scale';
+
+import {DragStrategy} from './drag_strategy';
+
+export class BorderDragStrategy extends DragStrategy {
+  private pixelBounds: [number, number];
+  private moveStart = false;
+
+  constructor(timeScale: TimeScale, pixelBounds: [number, number]) {
+    super(timeScale);
+    this.pixelBounds = pixelBounds;
+  }
+
+  onDrag(x: number) {
+    let tStart =
+        this.timeScale.pxToTime(this.moveStart ? x : this.pixelBounds[0]);
+    let tEnd =
+        this.timeScale.pxToTime(!this.moveStart ? x : this.pixelBounds[1]);
+    if (tStart > tEnd) {
+      [tStart, tEnd] = [tEnd, tStart];
+      this.moveStart = !this.moveStart;
+    }
+    super.updateGlobals(tStart, tEnd);
+  }
+
+  onDragStart(x: number) {
+    this.moveStart =
+        Math.abs(x - this.pixelBounds[0]) < Math.abs(x - this.pixelBounds[1]);
+  }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/drag/drag_strategy.ts b/ui/src/frontend/drag/drag_strategy.ts
new file mode 100644
index 0000000..1d272d7
--- /dev/null
+++ b/ui/src/frontend/drag/drag_strategy.ts
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import {TimeSpan} from '../../common/time';
+import {globals} from '../globals';
+import {TimeScale} from '../time_scale';
+
+export abstract class DragStrategy {
+  protected timeScale: TimeScale;
+
+  constructor(timeScale: TimeScale) {
+    this.timeScale = timeScale;
+  }
+
+  abstract onDrag(x: number): void;
+
+  abstract onDragStart(x: number): void;
+
+  protected updateGlobals(tStart: number, tEnd: number) {
+    const vizTime = new TimeSpan(tStart, tEnd);
+    globals.frontendLocalState.updateVisibleTime(vizTime);
+    globals.rafScheduler.scheduleRedraw();
+  }
+}
diff --git a/ui/src/frontend/drag/inner_drag_strategy.ts b/ui/src/frontend/drag/inner_drag_strategy.ts
new file mode 100644
index 0000000..a4a0d44
--- /dev/null
+++ b/ui/src/frontend/drag/inner_drag_strategy.ts
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import {TimeScale} from '../time_scale';
+import {DragStrategy} from './drag_strategy';
+
+export class InnerDragStrategy extends DragStrategy {
+  private dragStartPx = 0;
+  private pixelBounds: [number, number];
+
+  constructor(timeScale: TimeScale, pixelBounds: [number, number]) {
+    super(timeScale);
+    this.pixelBounds = pixelBounds;
+  }
+
+  onDrag(x: number) {
+    const move = x - this.dragStartPx;
+    const tStart = this.timeScale.pxToTime(this.pixelBounds[0] + move);
+    const tEnd = this.timeScale.pxToTime(this.pixelBounds[1] + move);
+    super.updateGlobals(tStart, tEnd);
+  }
+
+  onDragStart(x: number) {
+    this.dragStartPx = x;
+  }
+}
diff --git a/ui/src/frontend/drag/outer_drag_strategy.ts b/ui/src/frontend/drag/outer_drag_strategy.ts
new file mode 100644
index 0000000..bde417c
--- /dev/null
+++ b/ui/src/frontend/drag/outer_drag_strategy.ts
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import {DragStrategy} from './drag_strategy';
+
+export class OuterDragStrategy extends DragStrategy {
+  private dragStartPx = 0;
+
+  onDrag(x: number) {
+    let tStart = this.timeScale.pxToTime(this.dragStartPx);
+    let tEnd = this.timeScale.pxToTime(x);
+    if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart];
+    super.updateGlobals(tStart, tEnd);
+  }
+
+  onDragStart(x: number) {
+    this.dragStartPx = x;
+  }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 9e51317..4349f81 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -267,6 +267,10 @@
     this.ratelimitedUpdateVisible();
   }
 
+  getVisibleStateBounds(): [number, number] {
+    return [this.visibleWindowTime.start, this.visibleWindowTime.end];
+  }
+
   // Whenever start/end px of the timeScale is changed, update
   // the resolution.
   updateLocalLimits(pxStart: number, pxEnd: number) {
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 23507ee..37fd1a9 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -19,6 +19,10 @@
 import {TimeSpan, timeToString} from '../common/time';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
+import {BorderDragStrategy} from './drag/border_drag_strategy';
+import {DragStrategy} from './drag/drag_strategy';
+import {InnerDragStrategy} from './drag/inner_drag_strategy';
+import {OuterDragStrategy} from './drag/outer_drag_strategy';
 import {DragGestureHandler} from './drag_gesture_handler';
 import {globals} from './globals';
 import {Panel, PanelSize} from './panel';
@@ -26,10 +30,11 @@
 
 export class OverviewTimelinePanel extends Panel {
   private width = 0;
-  private dragStartPx = 0;
   private gesture?: DragGestureHandler;
   private timeScale?: TimeScale;
   private totTime = new TimeSpan(0, 0);
+  private static BORDER_PIXEL_DELTA = 30;
+  private dragStrategy?: DragStrategy;
 
   // Must explicitly type now; arguments types are no longer auto-inferred.
   // https://github.com/Microsoft/TypeScript/issues/1373
@@ -123,21 +128,33 @@
   }
 
   onDrag(x: number) {
-    // Set visible time limits from selection.
-    if (this.timeScale === undefined) return;
-    let tStart = this.timeScale.pxToTime(this.dragStartPx);
-    let tEnd = this.timeScale.pxToTime(x);
-    if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart];
-    const vizTime = new TimeSpan(tStart, tEnd);
-    globals.frontendLocalState.updateVisibleTime(vizTime);
-    globals.rafScheduler.scheduleRedraw();
+    if (this.dragStrategy === undefined) return;
+    this.dragStrategy.onDrag(x);
   }
 
   onDragStart(x: number) {
-    this.dragStartPx = x;
+    if (this.timeScale === undefined) return;
+    const timeSpan = globals.frontendLocalState.getVisibleStateBounds();
+    const pixelBounds: [number, number] = [
+      this.timeScale.timeToPx(timeSpan[0]),
+      this.timeScale.timeToPx(timeSpan[1])
+    ];
+    if (OverviewTimelinePanel.inBorderRange(x, pixelBounds[0]) ||
+        OverviewTimelinePanel.inBorderRange(x, pixelBounds[1])) {
+      this.dragStrategy = new BorderDragStrategy(this.timeScale, pixelBounds);
+    } else if (x < pixelBounds[0] || pixelBounds[1] < x) {
+      this.dragStrategy = new OuterDragStrategy(this.timeScale);
+    } else {
+      this.dragStrategy = new InnerDragStrategy(this.timeScale, pixelBounds);
+    }
+    this.dragStrategy.onDragStart(x);
   }
 
   onDragEnd() {
-    this.dragStartPx = 0;
+    this.dragStrategy = undefined;
+  }
+
+  private static inBorderRange(a: number, b: number): boolean {
+    return Math.abs(a - b) < this.BORDER_PIXEL_DELTA;
   }
 }