import L, { DomEvent, DomUtil } from 'leaflet';
import { HrMap } from './Map.class';
type PaneGet2Order = 'cover' | 'cover2' | 'cover3' | 'min' | 'max' | number;
/**
* 0 - no
* 1 - loop
* 2 - break
*/
let phaseOfMouseEventHandleLoopFrame = 0;
let fireEventCall = 0; // on called counter.
console.log('warn!. L.Canvas prototype was overrided.');
(L.Canvas.prototype as any)._initContainer = function () {
const container = (this._container = document.createElement('canvas'));
// These 3 lines are deleted for we will use a proxy pane.
// DomEvent.on(container, 'mousemove', this._onMouseMove, this);
// DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
// DomEvent.on(container, 'mouseout', this._handleMouseOut, this);
container['_leaflet_disable_events'] = true; // 1.8
this._ctx = container.getContext('2d');
};
(L.Canvas.prototype as any)._fireEvent = function (layers, e, type) {
const map = this._map as HrMap;
if (this.__of_proxy_pane__) {
map._fireDOMEvent(e, type || e.type, layers);
return;
}
/**
* 这个是保持 leaflet 默认的事件发射逻辑
*/
if (phaseOfMouseEventHandleLoopFrame === 0) {
// default
map._fireDOMEvent(e, type || e.type, layers);
return;
}
/**
* 检测到物体,停止设置 phaseOfMouseEventHandleLoopFrame = 2 表示要中止遍历,因为目的已经达到
*/
if (layers && phaseOfMouseEventHandleLoopFrame === 1) {
map._fireDOMEvent(e, type || e.type, layers);
phaseOfMouseEventHandleLoopFrame = 2;
return;
}
/**
* 全部遍历完毕,没有检测到物体
*/
if (fireEventCall === map.__canvas_renderers_size__) {
map._fireDOMEvent(e, type || e.type, layers);
}
};
// IT's really un
(L.Canvas.prototype as any)._handleMouseHover = function (e, point) {
if (this._mouseHoverThrottled) {
return;
}
let layer, candidateHoveredLayer;
for (let order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
candidateHoveredLayer = layer;
}
}
if (candidateHoveredLayer !== this._hoveredLayer) {
this._handleMouseOut(e);
if (candidateHoveredLayer) {
DomUtil.addClass(this.$$proxyc, 'leaflet-interactive'); // change cursor
this._fireEvent([candidateHoveredLayer], e, 'mouseover');
this._hoveredLayer = candidateHoveredLayer;
}
}
this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
this._mouseHoverThrottled = true;
setTimeout(() => {
this._mouseHoverThrottled = false;
}, 32);
};
(L.Canvas.prototype as any)._handleMouseOut = function (e) {
const layer = this._hoveredLayer;
if (layer) {
// if we're leaving the layer, fire mouseout
DomUtil.removeClass(this.$$proxyc, 'leaflet-interactive');
this._fireEvent([layer], e, 'mouseout');
this._hoveredLayer = null;
this._mouseHoverThrottled = false;
}
};
export type PaneType = 'svg' | 'canvas' | 'overlay';
/**
* 一个 pane 只放一个 renderer
*/
export class Pane {
/**
* L.Canvas Or L.SVG
*/
readonly renderer: any;
readonly type: PaneType;
readonly name: string;
readonly fullname: string;
readonly container: HTMLDivElement;
/**
* 是否 canvas 渲染
*/
readonly canvas: boolean = false;
/**
* 是否代理层
*/
readonly proxy: boolean = false;
/**
* 排序
*/
order: number = 0;
/**
*是否显示
*/
visible: boolean = true;
/**
* 绝对 z 值
*/
z: number = null;
/**
* 序号
*/
i: number = 0;
/**
* 挂载的 layer 的个数
*/
used: number = 0;
/**
* true 表示会放到图层面板
*
* false 表示只是临时生成的 renderer,用完可能会删除
*/
configurable: boolean = true;
/**
* parent
*/
mgr: PaneManager = null;
/**
* 禁止响应
*/
disabled: boolean = false;
constructor(
name: string,
type: PaneType,
container: HTMLDivElement,
renderer: L.Renderer = null,
) {
this.name = name;
this.fullname = `${this.name}Pane`;
this.type = type;
this.renderer = renderer;
this.container = container;
this.canvas = type === 'canvas';
this.proxy = this.name === 'proxy';
this.configurable = this.proxy ? false : true;
if (this.renderer) {
this.renderer['__of_proxy_pane__'] = this.proxy;
}
}
_remove() {
this.renderer?.remove();
this.container.remove();
}
remove() {
this.mgr.remove(this);
}
get zIndex() {
if (this.z !== null) {
return this.z;
}
switch (this.type) {
case 'overlay':
return 501 + this.i;
case 'svg':
return 451 + this.i;
case 'canvas': {
return 401 + this.i;
}
default: {
return 400;
}
}
}
}
const orderSort = (a: Pane, b: Pane) => b.order - a.order;
// canvas 402 - 450
// svg 451 - 499
// overlay 500 - 599
/**
* @event order/add/remove
*/
export class PaneManager extends L.Evented {
private styleElement: HTMLStyleElement = null;
private panes: Pane[] = [];
private panesDict: Record<string, Pane> = {};
private canvases: Pane[] = [];
/**
* proxy pane's renderer.
*/
private proxy: any = null;
/**
* 全部层的父级节点
*/
container: HTMLDivElement = null;
/**
* 禁止响应
*/
disabled = false;
constructor(private map: HrMap) {
super();
this.container = map._container;
this.map.__canvas_renderers_size__ = 0;
Object.defineProperty(this.map, 'paneMgr', { value: this });
}
private _scheduled = false;
requestRender() {
if (this._scheduled) return;
queueMicrotask(() => {
this.renderStyle();
this._scheduled = false;
});
this._scheduled = true;
}
renderStyle() {
this.panes.sort(orderSort);
let i = this.panes.length;
for (const pane of this.panes) {
pane.i = i--;
}
this.canvases = this.panes.filter((p) => p.canvas);
this.styleElement.innerHTML = `
.leaflet-pane.leaflet-proxy-pane {
z-index: 450 !important;
}
${this.panes
.map((pane) => {
return ` .leaflet-pane.leaflet-${pane.name}-pane {
z-index: ${pane.zIndex} !important;
display: ${pane.visible ? 'block' : 'none'}
}`;
})
.join('\n')}
`;
this.fire('render');
}
createStyle() {
this.styleElement = document.createElement('style');
this.styleElement.id = 'panes';
document.head.appendChild(this.styleElement);
}
setOrder(name: string, order: PaneGet2Order) {
const pane = this._get(name);
this._setOrder(pane, order);
this.requestRender();
this.fire('order');
}
_setOrder(pane: Pane, order: PaneGet2Order) {
if (typeof order === 'number') {
pane.order = order;
pane.z = null;
} else if (order === 'cover') {
pane.z = 451;
} else if (order === 'cover2') {
pane.z = 500;
} else if (order === 'cover3') {
pane.z = 599;
} else if (order === 'max') {
pane.order = Math.max(...this.panes.filter((p) => p.configurable).map((x) => x.order)) + 1;
pane.z = null;
} else if (order === 'min') {
pane.order = Math.min(...this.panes.filter((p) => p.configurable).map((x) => x.order)) - 1;
pane.z = null;
} else {
//
}
}
setTop(name: string) {
this.setOrder(name, 'max');
}
setVisible(name: string, visible: boolean) {
const pane = this._get(name);
pane.visible = visible;
this.requestRender();
this.fire('visible');
}
toggleVisible(name: string) {
const pane = this._get(name);
if (!name) return;
this.setVisible(pane.name, !pane.visible);
}
_get(name: string) {
return this.panesDict[name];
}
has(name: string) {
return !!this.panesDict[name];
}
/**
* 排除 used 为 0 的项目
*/
getPanes() {
return this.panes.filter((x) => x.configurable && x.used);
}
/**
* get 可以帮助你创建并加入管理
*/
get(name: string, type: PaneType = 'canvas', order: number = 0) {
let pane = this._get(name);
if (pane) {
pane.order = order;
} else {
pane = this.create(name, type, order);
this.add(pane);
}
return pane;
}
/**
* get2 创建不受管理的 pane
*/
get2(type: PaneType, order: PaneGet2Order = 'min', name: string = null) {
const _name = name || `p${Math.random().toString(36).substring(2)}`;
if (this.has(_name)) {
return this._get(_name);
}
const pane = this.create(_name, type, 0);
this._setOrder(pane, order);
pane.configurable = false;
this.add(pane);
return pane;
}
add(pane: Pane) {
this.panes.unshift(pane);
this.panesDict[pane.name] = pane;
if (pane.type === 'canvas') {
this.map.__canvas_renderers_size__ += 1;
/**
* 将 proxy 的 canvas 元素挂载到当前 renderer,用于鼠标交互时访问
*/
pane.renderer.$$proxyc = this.proxy._container;
}
pane.mgr = this;
this.requestRender();
this.fire('add');
}
remove(name: string | Pane) {
const pane = typeof name === 'string' ? this._get(name) : name;
if (!pane) return;
this.panes = this.panes.filter((x) => x !== pane);
delete this.panesDict[pane.name];
pane._remove();
if (pane.type === 'canvas') {
this.map.__canvas_renderers_size__ -= 1;
}
this.requestRender();
this.fire('remove');
}
/**
* @param name without "Pane" suffix
* @param type svg or canvas
* @param z default order must
* @returns
*/
create(name: string, type: PaneType, order = 0): Pane {
const fullname = `${name}Pane`;
let container: HTMLDivElement = null;
let renderer: L.Renderer = null;
if ('map,tile,overlay,shadow,marker,tooltip,popup'.indexOf(name) > -1) {
container = this.container.querySelector(`.leaflet-${name}-pane`);
} else {
container = this.map.createPane(`${name}Pane`) as HTMLDivElement;
}
if (type !== 'overlay') {
renderer =
type === 'canvas' ? new L.Canvas({ pane: fullname }) : new L.SVG({ pane: fullname });
}
const pane = new Pane(name, type, container, renderer);
pane.order = order;
return pane;
}
interact() {
const pane = this.create('proxy', 'canvas');
this.map.addLayer(pane.renderer);
this.proxy = pane.renderer;
const container = pane.renderer._container;
DomEvent.on(container, 'mousemove', this.onmousemove, this);
DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this.onclick, this);
DomEvent.on(container, 'mouseout', this.onmouseout, this);
this.createStyle();
this.renderStyle();
}
/**
* 存在的问题: 货架遮挡在点之上,move 事件还是会对被遮挡的点起作用,因为,move 此时不会对货架产生 hover 事件
*/
private onmousemove(e) {
if (this.disabled) {
this.proxy._onMouseMove(e);
return;
}
phaseOfMouseEventHandleLoopFrame = 1;
fireEventCall = 0;
for (const { renderer, visible, disabled } of this.canvases) {
fireEventCall++;
if (phaseOfMouseEventHandleLoopFrame === 2) continue;
if (!renderer._map || !visible || disabled) continue;
renderer._onMouseMove(e);
}
fireEventCall = 0;
phaseOfMouseEventHandleLoopFrame = 0;
}
private onclick(e) {
if (this.disabled) {
this.proxy._onClick(e);
return;
}
phaseOfMouseEventHandleLoopFrame = 1;
fireEventCall = 0;
for (const { renderer, visible, disabled } of this.canvases) {
fireEventCall++;
if (phaseOfMouseEventHandleLoopFrame === 2) continue;
if (!renderer._map || !visible || disabled) continue;
renderer._onClick(e);
}
fireEventCall = 0;
phaseOfMouseEventHandleLoopFrame = 0;
}
private onmouseout(e) {
if (this.disabled) {
this.proxy._handleMouseOut(e);
return;
}
phaseOfMouseEventHandleLoopFrame = 1;
fireEventCall = 0;
for (const { renderer, visible, disabled } of this.canvases) {
fireEventCall++;
if (phaseOfMouseEventHandleLoopFrame === 2) continue;
if (!renderer._map || !visible || disabled) continue;
renderer._handleMouseOut(e);
}
fireEventCall = 0;
phaseOfMouseEventHandleLoopFrame = 0;
}
}