diff --git a/src/base-gl-layer.ts b/src/base-gl-layer.ts index 06f9fa2..352aa2b 100644 --- a/src/base-gl-layer.ts +++ b/src/base-gl-layer.ts @@ -35,6 +35,7 @@ export interface IBaseGlLayerSettings { }; setupClick?: (map: Map) => void; setupContextMenu?: (map: Map) => void; + setupDblClick?: (map: Map) => void; setupHover?: SetupHoverCallback; sensitivity?: number; sensitivityHover?: number; @@ -42,6 +43,7 @@ export interface IBaseGlLayerSettings { fragmentShaderSource?: (() => string) | string; canvas?: HTMLCanvasElement; click?: EventCallback; + dblClick?: EventCallback; contextMenu?: EventCallback; hover?: EventCallback; hoverOff?: EventCallback; @@ -237,6 +239,9 @@ export abstract class BaseGlLayer< if (settings.click && settings.setupClick) { settings.setupClick(this.map); } + if (settings.dblClick && settings.setupDblClick) { + settings.setupDblClick(this.map); + } if (settings.contextMenu && settings.setupContextMenu) { settings.setupContextMenu(this.map); } @@ -422,6 +427,14 @@ export abstract class BaseGlLayer< } } + dblClick(e: LeafletMouseEvent, feature: any): boolean | undefined { + if (!this.settings.dblClick) return; + const result = this.settings.dblClick(e, feature); + if (result !== undefined) { + return result; + } + } + contextMenu(e: LeafletMouseEvent, feature: any): boolean | undefined { if (!this.settings.contextMenu) return; const result = this.settings.contextMenu(e, feature); diff --git a/src/index.ts b/src/index.ts index a8fc471..164b668 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,6 +29,7 @@ export class Glify { longitudeKey = 1; latitudeKey = 0; clickSetupMaps: Map[] = []; + dblClickSetupMaps: Map[] = []; contextMenuSetupMaps: Map[] = []; hoverSetupMaps: Map[] = []; shader = shader; @@ -64,6 +65,7 @@ export class Glify { points(settings: Partial): Points { const points = new this.Points({ setupClick: this.setupClick.bind(this), + setupDblClick: this.setupDblClick.bind(this), setupContextMenu: this.setupContextMenu.bind(this), setupHover: this.setupHover.bind(this), latitudeKey: glify.latitudeKey, @@ -83,6 +85,7 @@ export class Glify { lines(settings: Partial): Lines { const lines = new this.Lines({ setupClick: this.setupClick.bind(this), + setupDblClick: this.setupDblClick.bind(this), setupContextMenu: this.setupContextMenu.bind(this), setupHover: this.setupHover.bind(this), latitudeKey: this.latitudeKey, @@ -102,6 +105,7 @@ export class Glify { shapes(settings: Partial): Shapes { const shapes = new this.Shapes({ setupClick: this.setupClick.bind(this), + setupDblClick: this.setupDblClick.bind(this), setupContextMenu: this.setupContextMenu.bind(this), setupHover: this.setupHover.bind(this), latitudeKey: this.latitudeKey, @@ -134,6 +138,22 @@ export class Glify { }); } + setupDblClick(map: Map): void { + if (this.dblClickSetupMaps.includes(map)) return; + this.dblClickSetupMaps.push(map); + map.on("dblclick", (e: LeafletMouseEvent) => { + let hit; + hit = this.Points.tryDblClick(e, map, this.pointsInstances); + if (hit !== undefined) return hit; + + hit = this.Lines.tryDblClick(e, map, this.linesInstances); + if (hit !== undefined) return hit; + + hit = this.Shapes.tryDblClick(e, map, this.shapesInstances); + if (hit !== undefined) return hit; + }) + } + setupContextMenu(map: Map): void { if (this.contextMenuSetupMaps.includes(map)) return; this.clickSetupMaps.push(map); diff --git a/src/lines.ts b/src/lines.ts index 51d872c..d84b011 100644 --- a/src/lines.ts +++ b/src/lines.ts @@ -413,6 +413,89 @@ export class Lines extends BaseGlLayer { } } + // attempts to double click the top-most Lines instance + static tryDblClick( + e: LeafletMouseEvent, + map: Map, + instances: Lines[] + ): boolean | undefined { + let foundFeature: Feature | null = null; + let foundLines: Lines | null = null; + + instances.forEach((instance: Lines): void => { + const { latitudeKey, longitudeKey, sensitivity, weight, scale, active } = + instance; + if (!active) return; + if (instance.map !== map) return; + function checkClick( + coordinate: Position, + prevCoordinate: Position, + feature: Feature, + chosenWeight: number + ): void { + const distance = latLngDistance( + e.latlng.lng, + e.latlng.lat, + prevCoordinate[longitudeKey], + prevCoordinate[latitudeKey], + coordinate[longitudeKey], + coordinate[latitudeKey] + ); + if (distance <= sensitivity + chosenWeight / scale) { + foundFeature = feature; + foundLines = instance; + } + } + instance.data.features.forEach( + (feature: Feature, i: number): void => { + const chosenWeight = + typeof weight === "function" ? weight(i, feature) : weight; + const { coordinates, type } = feature.geometry; + if (type === "LineString") { + for (let i = 1; i < coordinates.length; i++) { + checkClick( + coordinates[i] as Position, + coordinates[i - 1] as Position, + feature, + chosenWeight + ); + } + } else if (type === "MultiLineString") { + // TODO: Unit test + for (let i = 0; i < coordinates.length; i++) { + const coordinate = coordinates[i]; + for (let j = 0; j < coordinate.length; j++) { + if (j === 0 && i > 0) { + const prevCoordinates = coordinates[i - 1]; + const lastPositions = + prevCoordinates[prevCoordinates.length - 1]; + checkClick( + lastPositions as Position, + coordinates[i][j] as Position, + feature, + chosenWeight + ); + } else if (j > 0) { + checkClick( + coordinates[i][j] as Position, + coordinates[i][j - 1] as Position, + feature, + chosenWeight + ); + } + } + } + } + } + ); + }); + + if (foundLines && foundFeature) { + const result = (foundLines as Lines).dblClick(e, foundFeature); + return result !== undefined ? result : undefined; + } + } + // attempts to click the top-most Lines instance static tryContextMenu( e: LeafletMouseEvent, diff --git a/src/points.ts b/src/points.ts index fa4036b..4c579fa 100644 --- a/src/points.ts +++ b/src/points.ts @@ -435,6 +435,50 @@ export class Points extends BaseGlLayer { } } + // attempts to click the top-most Points instance + static tryDblClick( + e: LeafletMouseEvent, + map: Map, + instances: Points[] + ): boolean | undefined { + const closestFromEach: IPointVertex[] = []; + const instancesLookup: { [key: string]: Points } = {}; + let result; + let settings: Partial | null = null; + let pointLookup: IPointVertex | null; + + instances.forEach((_instance: Points) => { + settings = _instance.settings; + if (!_instance.active) return; + if (_instance.map !== map) return; + + pointLookup = _instance.lookup(e.latlng); + if (pointLookup === null) return; + instancesLookup[pointLookup.key] = _instance; + closestFromEach.push(pointLookup); + }); + + if (closestFromEach.length < 1) return; + if (!settings) return; + + const found = this.closest(e.latlng, closestFromEach, map); + + if (!found) return; + + const instance = instancesLookup[found.key]; + if (!instance) return; + const { sensitivity } = instance; + const foundLatLng = found.latLng; + const xy = map.latLngToLayerPoint(foundLatLng); + + if ( + pixelInCircle(xy, e.layerPoint, found.chosenSize * (sensitivity ?? 1)) + ) { + result = instance.dblClick(e, found.feature || found.latLng); + return result !== undefined ? result : true; + } + } + // attempts to click the top-most Points instance static tryContextMenu( e: LeafletMouseEvent, diff --git a/src/shapes.ts b/src/shapes.ts index 267bd37..5107878 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -391,6 +391,35 @@ export class Shapes extends BaseGlLayer { } } + // attempts to double click the top-most Shapes instance + static tryDblClick( + e: LeafletMouseEvent, + map: Map, + instances: Shapes[] + ): boolean | undefined { + let foundPolygon: Feature | null = null; + let foundShapes: Shapes | null = null; + instances.forEach(function (_instance: Shapes): void { + if (!_instance.active) return; + if (_instance.map !== map) return; + if (!_instance.polygonLookup) return; + + const polygon = _instance.polygonLookup.search( + e.latlng.lng, + e.latlng.lat + ); + if (polygon) { + foundShapes = _instance; + foundPolygon = polygon; + } + }); + + if (foundShapes && foundPolygon) { + const result = (foundShapes as Shapes).dblClick(e, foundPolygon); + return result !== undefined ? result : undefined; + } + } + // attempts to click the top-most Shapes instance static tryContextMenu( e: LeafletMouseEvent,