1+ import { IMap } from '../types'
2+
3+ type MercatorCoordinate = {
4+ x : number ,
5+ y : number ,
6+ z : number
7+ }
8+
9+ class Point {
10+ /**
11+ *
12+ */
13+ constructor ( public x : number , public y : number ) {
14+
15+ }
16+
17+ dist ( point : Point ) {
18+ return Math . sqrt ( Math . pow ( ( this . x - point . x ) , 2 ) + Math . pow ( ( this . y - point . y ) , 2 ) ) ;
19+ }
20+
21+ sub ( point : Point ) {
22+ return new Point ( this . x - point . x , this . y - point . y ) ;
23+ }
24+ }
25+
26+ type HandlerResult = {
27+ panDelta ?: Point ,
28+ zoomDelta ?: number ,
29+ bearingDelta ?: number ,
30+ pitchDelta ?: number ,
31+ // the point to not move when changing the camera
32+ around ?: Point | null ,
33+ // same as above, except for pinch actions, which are given higher priority
34+ pinchAround ?: Point | null ,
35+ // the point to not move when changing the camera in mercator coordinates
36+ aroundCoord ?: MercatorCoordinate | null ,
37+ // A method that can fire a one-off easing by directly changing the map's camera.
38+ cameraAnimation ?: ( map : IMap ) => any ;
39+
40+ // The last three properties are needed by only one handler: scrollzoom.
41+ // The DOM event to be used as the `originalEvent` on any camera change events.
42+ originalEvent ?: any ,
43+ // Makes the manager trigger a frame, allowing the handler to return multiple results over time (see scrollzoom).
44+ needsRenderFrame ?: boolean ,
45+ // The camera changes won't get recorded for inertial zooming.
46+ noInertia ?: boolean
47+ } ;
48+
49+ namespace DOM {
50+ export function mouseButton ( e : MouseEvent ) : number {
51+ if ( e . type === 'mousedown' || e . type === 'mouseup' ) {
52+ if ( typeof ( window as any ) . InstallTrigger !== 'undefined' && e . button === 2 && e . ctrlKey &&
53+ window . navigator . platform . toUpperCase ( ) . indexOf ( 'MAC' ) >= 0 ) {
54+ // Fix for https://github.com/mapbox/mapbox-gl-js/issues/3131:
55+ // Firefox (detected by InstallTrigger) on Mac determines e.button = 2 when
56+ // using Control + left click
57+ return 0 ;
58+ }
59+
60+ return e . button as any ;
61+ }
62+
63+ return - 1 ;
64+ }
65+
66+ // Suppress the next click, but only if it's immediate.
67+ function suppressClickListener ( e : Event ) {
68+ e . preventDefault ( ) ;
69+ e . stopPropagation ( ) ;
70+ window . removeEventListener ( 'click' , suppressClickListener , true ) ;
71+ }
72+
73+ export function suppressClick ( ) {
74+ window . addEventListener ( 'click' , suppressClickListener , true ) ;
75+ window . setTimeout ( ( ) => {
76+ window . removeEventListener ( 'click' , suppressClickListener , true ) ;
77+ } , 0 ) ;
78+ }
79+
80+ const docStyle = window . document && window . document . documentElement . style ;
81+ const selectProp = docStyle && docStyle . userSelect !== undefined ? 'userSelect' : 'WebkitUserSelect' ;
82+ let userSelect : any ;
83+
84+ export function disableDrag ( ) {
85+ if ( docStyle && selectProp ) {
86+ userSelect = docStyle [ selectProp as any ] ;
87+ docStyle [ selectProp as any ] = 'none' ;
88+ }
89+ }
90+
91+ export function enableDrag ( ) {
92+ if ( docStyle && selectProp ) {
93+ docStyle [ selectProp as any ] = userSelect ;
94+ }
95+ }
96+
97+ export function mousePos ( el : HTMLElement , e : MouseEvent | WheelEvent ) : Point {
98+ const rect = el . getBoundingClientRect ( ) ;
99+ return getScaledPoint ( el , rect , e ) ;
100+ }
101+
102+ function getScaledPoint ( el : HTMLElement , rect : ClientRect , e : MouseEvent | WheelEvent | Touch ) {
103+ // Until we get support for pointer events (https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent)
104+ // we use this dirty trick which would not work for the case of rotated transforms, but works well for
105+ // the case of simple scaling.
106+ // Note: `el.offsetWidth === rect.width` eliminates the `0/0` case.
107+ const scaling = el . offsetWidth === rect . width ? 1 : el . offsetWidth / rect . width ;
108+ return new Point (
109+ ( e . clientX - rect . left ) * scaling ,
110+ ( e . clientY - rect . top ) * scaling
111+ ) ;
112+ }
113+ }
114+
115+ abstract class MouseHandler {
116+
117+ _enabled : boolean = true ;
118+ _active ?: boolean ;
119+ _lastPoint ?: Point ;
120+ _eventButton ?: number ;
121+ _moved ?: boolean ;
122+ _clickTolerance : number ;
123+
124+ constructor ( options : { clickTolerance ?: number } ) {
125+ this . reset ( ) ;
126+ this . _clickTolerance = options . clickTolerance || 1 ;
127+ }
128+
129+ blur ( ) {
130+ this . reset ( ) ;
131+ }
132+
133+ reset ( ) {
134+ this . _active = false ;
135+ this . _moved = false ;
136+ this . _lastPoint = undefined ;
137+ this . _eventButton = undefined ;
138+ }
139+
140+ abstract correctButton ( e : MouseEvent , button : number ) : boolean ;
141+
142+ abstract move ( lastPoint : Point , point : Point ) : HandlerResult ;
143+
144+ mousedown ( e : MouseEvent , point : Point ) {
145+ if ( this . _lastPoint ) return ;
146+
147+ const eventButton = DOM . mouseButton ( e ) ;
148+ if ( ! this . correctButton ( e , eventButton ) ) return ;
149+
150+ this . _lastPoint = point ;
151+ this . _eventButton = eventButton ;
152+ }
153+
154+ mousemoveWindow ( e : MouseEvent , point : Point ) : HandlerResult | undefined {
155+ const lastPoint = this . _lastPoint ;
156+ if ( ! lastPoint ) return ;
157+ e . preventDefault ( ) ;
158+
159+ if ( ! this . _moved && point . dist ( lastPoint ) < this . _clickTolerance ) return ;
160+ this . _moved = true ;
161+ this . _lastPoint = point ;
162+
163+ // implemented by child class
164+ return this . move ( lastPoint , point ) ;
165+ }
166+
167+ mouseupWindow ( e : MouseEvent ) {
168+ if ( ! this . _lastPoint ) return ;
169+ const eventButton = DOM . mouseButton ( e ) ;
170+ if ( eventButton !== this . _eventButton ) return ;
171+ if ( this . _moved ) DOM . suppressClick ( ) ;
172+ this . reset ( ) ;
173+ }
174+
175+ enable ( ) {
176+ this . _enabled = true ;
177+ }
178+
179+ disable ( ) {
180+ this . _enabled = false ;
181+ this . reset ( ) ;
182+ }
183+
184+ isEnabled ( ) : boolean {
185+ return this . _enabled ;
186+ }
187+
188+ isActive ( ) : boolean {
189+ return this . _active === true ;
190+ }
191+ }
192+
193+ class MouseRotateHandler extends MouseHandler {
194+ correctButton ( e : MouseEvent , button : number ) : boolean {
195+ return button === 1 ;
196+ }
197+
198+ move ( lastPoint : Point , point : Point ) : HandlerResult {
199+ const degreesPerPixelMoved = 0.8 ;
200+ const bearingDelta = ( point . x - lastPoint . x ) * degreesPerPixelMoved ;
201+ this . _active = true ;
202+ return { bearingDelta } ;
203+ }
204+
205+ contextmenu ( e : MouseEvent ) {
206+ // prevent browser context menu when necessary; we don't allow it with rotation
207+ // because we can't discern rotation gesture start from contextmenu on Mac
208+ e . preventDefault ( ) ;
209+ }
210+ }
211+
212+ class MousePitchHandler extends MouseHandler {
213+ correctButton ( e : MouseEvent , button : number ) : boolean {
214+ return button === 1 ;
215+ }
216+
217+ move ( lastPoint : Point , point : Point ) : HandlerResult {
218+ const degreesPerPixelMoved = - 0.5 ;
219+ const pitchDelta = ( point . y - lastPoint . y ) * degreesPerPixelMoved ;
220+ this . _active = true ;
221+ return { pitchDelta } ;
222+ }
223+
224+ contextmenu ( e : MouseEvent ) {
225+ // prevent browser context menu when necessary; we don't allow it with rotation
226+ // because we can't discern rotation gesture start from contextmenu on Mac
227+ e . preventDefault ( ) ;
228+ }
229+ }
230+
231+
232+ export class MiddleButtonRoate {
233+ private mouseRotate : MouseRotateHandler ;
234+ private mousePitch ?: MousePitchHandler ;
235+ enable = true ;
236+
237+ /**
238+ *
239+ */
240+ constructor ( private map : IMap , options : {
241+ withPitch ?: boolean
242+ } = { } ) {
243+ options . withPitch ??= true ;
244+
245+ const element = map . getContainer ( ) ;
246+ this . map = map ;
247+ this . mouseRotate = new MouseRotateHandler ( { clickTolerance : ( map . dragRotate as any ) . _mouseRotate . _clickTolerance } ) ;
248+ if ( options . withPitch )
249+ this . mousePitch = new MousePitchHandler ( { clickTolerance : ( map . dragRotate as any ) . _mousePitch . _clickTolerance } ) ;
250+
251+ const mousemove = ( e : MouseEvent ) => {
252+ this . move ( e , DOM . mousePos ( element , e ) ) ;
253+ }
254+
255+ const mouseup = ( e : MouseEvent ) => {
256+ this . mouseRotate . mouseupWindow ( e ) ;
257+ this . mousePitch ?. mouseupWindow ( e ) ;
258+ offTemp ( ) ;
259+ }
260+
261+ const offTemp = ( ) => {
262+ DOM . enableDrag ( ) ;
263+ window . removeEventListener ( 'mousemove' , mousemove ) ;
264+ window . removeEventListener ( 'mouseup' , mouseup ) ;
265+ }
266+
267+ const mousedown = ( e : MouseEvent ) => {
268+ if ( ! this . enable ) return ;
269+
270+ this . down ( e , DOM . mousePos ( element , e ) ) ;
271+ window . addEventListener ( 'mousemove' , mousemove ) ;
272+ window . addEventListener ( 'mouseup' , mouseup ) ;
273+ }
274+
275+ element . addEventListener ( 'mousedown' , mousedown ) ;
276+ }
277+
278+ private down ( e : MouseEvent , point : Point ) {
279+ this . mouseRotate . mousedown ( e , point ) ;
280+ this . mousePitch ?. mousedown ( e , point ) ;
281+ DOM . disableDrag ( ) ;
282+ }
283+
284+ private move ( e : MouseEvent , point : Point ) {
285+ const map = this . map ;
286+ const r = this . mouseRotate . mousemoveWindow ( e , point ) ;
287+ const delta = r && r . bearingDelta ;
288+ if ( delta ) map . setBearing ( map . getBearing ( ) + delta ) ;
289+ if ( this . mousePitch ) {
290+ const p = this . mousePitch . mousemoveWindow ( e , point ) ;
291+ const delta = p && p . pitchDelta ;
292+ if ( delta ) map . setPitch ( map . getPitch ( ) + delta ) ;
293+ }
294+ }
295+ }
0 commit comments