• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {CanvasDrawer} from './canvas_drawer';
18import {DraggableCanvasObject} from './draggable_canvas_object';
19
20export type DragListener = (x: number, y: number) => void;
21export type DropListener = DragListener;
22
23export class CanvasMouseHandler {
24  // Ordered top most element to bottom most
25  private draggableObjects: DraggableCanvasObject[] = [];
26  private draggingObject: DraggableCanvasObject | undefined = undefined;
27
28  private onDrag = new Map<DraggableCanvasObject, DragListener>();
29  private onDrop = new Map<DraggableCanvasObject, DropListener>();
30
31  constructor(
32    private drawer: CanvasDrawer,
33    private defaultCursor: string = 'auto',
34    private onUnhandledMouseDown: (x: number, y: number) => void = (x, y) => {}
35  ) {
36    this.drawer.canvas.addEventListener('mousemove', (event) => {
37      this.handleMouseMove(event);
38    });
39    this.drawer.canvas.addEventListener('mousedown', (event) => {
40      this.handleMouseDown(event);
41    });
42    this.drawer.canvas.addEventListener('mouseup', (event) => {
43      this.handleMouseUp(event);
44    });
45    this.drawer.canvas.addEventListener('mouseout', (event) => {
46      this.handleMouseUp(event);
47    });
48  }
49
50  registerDraggableObject(
51    draggableObject: DraggableCanvasObject,
52    onDrag: DragListener,
53    onDrop: DropListener
54  ) {
55    this.onDrag.set(draggableObject, onDrag);
56    this.onDrop.set(draggableObject, onDrop);
57  }
58
59  notifyDrawnOnTop(draggableObject: DraggableCanvasObject) {
60    const foundIndex = this.draggableObjects.indexOf(draggableObject);
61    if (foundIndex !== -1) {
62      this.draggableObjects.splice(foundIndex, 1);
63    }
64    this.draggableObjects.unshift(draggableObject);
65  }
66
67  private handleMouseDown(e: MouseEvent) {
68    e.preventDefault();
69    e.stopPropagation();
70    const {mouseX, mouseY} = this.getPos(e);
71
72    const clickedObject = this.objectAt(mouseX, mouseY);
73    if (clickedObject !== undefined) {
74      this.draggingObject = clickedObject;
75    } else {
76      this.onUnhandledMouseDown(mouseX, mouseY);
77    }
78    this.updateCursor(mouseX, mouseY);
79  }
80
81  private handleMouseMove(e: MouseEvent) {
82    e.preventDefault();
83    e.stopPropagation();
84    const {mouseX, mouseY} = this.getPos(e);
85
86    if (this.draggingObject !== undefined) {
87      const onDragCallback = this.onDrag.get(this.draggingObject);
88      if (onDragCallback !== undefined) {
89        onDragCallback(mouseX, mouseY);
90      }
91    }
92
93    this.updateCursor(mouseX, mouseY);
94  }
95
96  private handleMouseUp(e: MouseEvent) {
97    e.preventDefault();
98    e.stopPropagation();
99    const {mouseX, mouseY} = this.getPos(e);
100
101    if (this.draggingObject !== undefined) {
102      const onDropCallback = this.onDrop.get(this.draggingObject);
103      if (onDropCallback !== undefined) {
104        onDropCallback(mouseX, mouseY);
105      }
106    }
107
108    this.draggingObject = undefined;
109    this.updateCursor(mouseX, mouseY);
110  }
111
112  private getPos(e: MouseEvent) {
113    let mouseX = e.offsetX;
114    const mouseY = e.offsetY;
115
116    if (mouseX < this.drawer.padding.left) {
117      mouseX = this.drawer.padding.left;
118    }
119
120    if (mouseX > this.drawer.getWidth() - this.drawer.padding.right) {
121      mouseX = this.drawer.getWidth() - this.drawer.padding.right;
122    }
123
124    return {mouseX, mouseY};
125  }
126
127  private updateCursor(mouseX: number, mouseY: number) {
128    const hoverObject = this.objectAt(mouseX, mouseY);
129    if (hoverObject !== undefined) {
130      if (hoverObject === this.draggingObject) {
131        this.drawer.canvas.style.cursor = 'grabbing';
132      } else {
133        this.drawer.canvas.style.cursor = 'grab';
134      }
135    } else {
136      this.drawer.canvas.style.cursor = this.defaultCursor;
137    }
138  }
139
140  private objectAt(mouseX: number, mouseY: number): DraggableCanvasObject | undefined {
141    for (const object of this.draggableObjects) {
142      object.definePath(this.drawer.ctx);
143      if (
144        this.drawer.ctx.isPointInPath(
145          mouseX * this.drawer.getXScale(),
146          mouseY * this.drawer.getYScale()
147        )
148      ) {
149        return object;
150      }
151    }
152
153    return undefined;
154  }
155}
156