-
Notifications
You must be signed in to change notification settings - Fork 208
/
SectionMarkers.ts
204 lines (175 loc) · 7.77 KB
/
SectionMarkers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module HyperModeling
*/
import { assert, BeEvent, Id64String } from "@itwin/core-bentley";
import { Point2d, Point3d, XAndY, XYAndZ } from "@itwin/core-geometry";
import { IModelReadRpcInterface } from "@itwin/core-common";
import {
BeButton, BeButtonEvent, Cluster, DecorateContext, IModelApp, InputSource, Marker, MarkerImage, MarkerSet, ScreenViewport, ViewClipTool,
} from "@itwin/core-frontend";
import { SectionDrawingLocationState } from "./SectionDrawingLocationState";
import { HyperModeling } from "./HyperModeling";
const markerSize = Point2d.create(40, 40);
/** A [Marker]($frontend) associated with a [[SectionDrawingLocationState]], displayed as a canvas decoration at the location of the section.
* Clicking on the marker toggles display of the section graphics. Mousing over the marker produces a toolbar with additional interactions.
* @see [[HyperModelingDecorator]] for a [Decorator]($frontend) capable of displaying section markers for each section drawing location.
* @see [[SectionMarkerHandler]] to customize the marker interactions.
* @public
*/
export class SectionMarker extends Marker {
/** The section drawing location state associated with the marker. */
public readonly state: SectionDrawingLocationState;
/** A description displayed as part of the tooltip when this marker is clustered with other markers. */
public readonly description: string;
/** @internal */
public readonly onMouseEnterEvent = new BeEvent<(marker: SectionMarker) => void>();
/** @internal */
public readonly onMouseButtonEvent = new BeEvent<(marker: SectionMarker) => void>();
/** @internal */
private _isActive = false;
/** Constructor, typically invoked indirectly via [[HyperModelingDecorator]].
* @param state The section drawing location state this marker will represent.
* @param pos The world coordinates at which to display the marker.
* @param description A brief description of this marker, used as part of the tooltip when this marker is part of a cluster.
* @param icon The icon displayed by the marker.
* @param tooltip Optional detailed tooltip displayed on mouse hover. If undefined, the `description` is used instead.
*/
public constructor(state: SectionDrawingLocationState) {
super(state.placement.origin.clone(), markerSize);
this.state = state;
const data = HyperModeling.getMarkerData(state.sectionType);
this.description = data.label;
if (data.image)
this.setImage(data.image);
this.setScaleFactor({ low: .2, high: 1.4 }); // make size 20% at back of frustum and 140% at front of frustum (if camera is on)
}
/** @internal */
public get isHilited(): boolean { return this._isHilited; }
/** Returns true if this is the "active" section marker. At most one marker is active at a given time.
* @see [[HyperModelingDecorator.activeMarker]].
* @see [[HyperModelingDecorator.setActiveMarker]].
* @see [[SectionMarkerHandler.toggleMarker]].
*/
public get isActive(): boolean {
return this._isActive;
}
/** @internal */
public setActive(active: boolean): void {
this._isActive = active;
}
/** @internal */
protected drawActive(ctx: CanvasRenderingContext2D) {
ctx.shadowBlur = 30;
ctx.shadowColor = "gold";
return false;
}
/** @internal */
public override drawDecoration(ctx: CanvasRenderingContext2D): void {
if (!this.isActive || !this.drawActive(ctx))
super.drawDecoration(ctx);
}
/** @internal */
public override onMouseEnter(ev: BeButtonEvent) {
// Lazily load the tooltip.
if (undefined === this.title) {
IModelReadRpcInterface.getClientForRouting(this.state.iModel.routingContext.token).getToolTipMessage(this.state.iModel.getRpcProps(), this.state.id).then((tooltipMsg) => {
this.title = IModelApp.formatElementToolTip(tooltipMsg);
}).catch((_) => {
this.title = this.description;
});
}
super.onMouseEnter(ev);
this.onMouseEnterEvent.raiseEvent(this);
}
/** @internal */
public override onMouseButton(ev: BeButtonEvent): boolean {
if (InputSource.Mouse === ev.inputSource && BeButton.Data === ev.button && ev.isDown && ev.viewport)
this.onMouseButtonEvent.raiseEvent(this);
return true; // Don't allow clicks to be sent to active tool...
}
/** @internal */
public override addMarker(context: DecorateContext) {
super.addMarker(context);
if (this.isHilited)
ViewClipTool.drawClip(context, this.state.clip, undefined, { fillClipPlanes: true, hasPrimaryPlane: true });
}
}
/** A Marker used to show a cluster of section locations.
* @internal
*/
export class SectionMarkerCluster extends Marker {
/** Create a new cluster marker */
constructor(location: XYAndZ, size: XAndY, cluster: Cluster<SectionMarker>, image: MarkerImage | Promise<MarkerImage> | undefined) {
super(location, size);
this.imageOffset = new Point3d(0, 30);
this.label = cluster.markers.length.toLocaleString();
this.labelColor = "black";
this.labelFont = "bold 14px sans-serif";
const maxLen = 10;
let title = "";
cluster.markers.forEach((marker, index: number) => {
if (index < maxLen) {
if (title !== "")
title += "<br>";
title += marker.description;
}
});
if (cluster.markers.length > maxLen)
title += "<br>...";
const div = document.createElement("div");
div.innerHTML = title;
this.title = div;
if (image)
this.setImage(image);
}
/** Show the cluster as a white circle with an outline */
public override drawFunc(ctx: CanvasRenderingContext2D): void {
ctx.beginPath();
ctx.strokeStyle = "#372528";
ctx.fillStyle = "white";
ctx.lineWidth = 5;
ctx.arc(0, 0, 13, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
public override onMouseButton(_ev: BeButtonEvent): boolean { return true; } // Don't allow clicks to be sent to active tool...
}
/** A [MarkerSet]($frontend) containing [[SectionMarker]]s identifying [SectionDrawingLocation]($backend)s within a spatial view.
* Typically used indirectly via [[HyperModelingDecorator]].
* @public
*/
export class SectionMarkerSet extends MarkerSet<SectionMarker> {
public override minimumClusterSize = 5;
/** Constructor
* @param viewport The viewport in which the markers are to be displayed.
* @param markers The markers to be displayed.
* @note Each marker's [[SectionDrawingLocationState]] must be associated with the same [IModelConnection]($frontend) as the viewport; any markers from other iModels will be omitted.
*/
public constructor(viewport: ScreenViewport, markers: SectionMarker[]) {
super(viewport);
for (const marker of markers) {
if (marker.state.iModel === viewport.iModel)
this.markers.add(marker);
}
}
/** The viewport in which the markers are to be displayed. */
public override get viewport(): ScreenViewport {
assert(undefined !== super.viewport);
return super.viewport;
}
/** @internal */
protected getClusterMarker(cluster: Cluster<SectionMarker>): Marker {
return new SectionMarkerCluster(cluster.getClusterLocation(), cluster.markers[0].size, cluster, cluster.markers[0].image);
}
/** Find the SectionMarker corresponding to the specified [SectionDrawingLocation]($backend) Id. */
public findMarkerById(sectionDrawingLocationId: Id64String): SectionMarker | undefined {
for (const marker of this.markers)
if (marker.state.id === sectionDrawingLocationId)
return marker;
return undefined;
}
}