Skip to content

Commit 14ad45c

Browse files
committed
Fix measure context toolbar to not move with cursor
1 parent 716fac4 commit 14ad45c

File tree

1 file changed

+61
-44
lines changed

1 file changed

+61
-44
lines changed

packages/itwin/measure-tools/src/widgets/MeasurementActionToolbar.tsx

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66
import * as React from "react";
77
import type { XAndY } from "@itwin/core-geometry";
88
import { Point2d } from "@itwin/core-geometry";
9-
import { RelativePosition } from "@itwin/appui-abstract";
109
import type { ItemProps, ToolbarActionItem } from "@itwin/appui-react";
11-
import { ActionButtonItemDef, CursorInformation, CursorPopupManager, ToolbarItemUtilities } from "@itwin/appui-react";
10+
import {
11+
ActionButtonItemDef,
12+
CursorInformation,
13+
PopupManager,
14+
PositionPopup,
15+
Toolbar,
16+
ToolbarItemUtilities
17+
} from "@itwin/appui-react";
18+
import { IModelApp } from "@itwin/core-frontend";
19+
import { Direction } from "@itwin/components-react";
20+
1221
import { FeatureTracking, MeasureToolsFeatures } from "../api/FeatureTracking.js";
1322
import type { Measurement, MeasurementPickContext } from "../api/Measurement.js";
1423
import { MeasurementManager } from "../api/MeasurementManager.js";
@@ -102,7 +111,6 @@ export class MeasurementActionDefinitions {
102111
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.unlock"),
103112
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.unlock"),
104113
execute: (args: Measurement[]) => {
105-
106114
args.forEach((m) => {
107115
if (m.isLocked) {
108116
m.isLocked = false;
@@ -124,7 +132,6 @@ export class MeasurementActionDefinitions {
124132
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.lock"),
125133
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.lock"),
126134
execute: (args: Measurement[]) => {
127-
128135
args.forEach((m) => {
129136
if (!m.isLocked) {
130137
m.isLocked = true;
@@ -145,7 +152,6 @@ export class MeasurementActionDefinitions {
145152
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurements"),
146153
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeaurements"),
147154
execute: (args: Measurement[]) => {
148-
149155
args.forEach((m) => {
150156
if (m.isVisible) {
151157
m.isVisible = false;
@@ -165,7 +171,6 @@ export class MeasurementActionDefinitions {
165171
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurements"),
166172
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurements"),
167173
execute: (args: Measurement[]) => {
168-
169174
args.forEach((m) => {
170175
if (!m.isVisible) {
171176
m.isVisible = true;
@@ -185,7 +190,6 @@ export class MeasurementActionDefinitions {
185190
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayLabels"),
186191
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayLabels"),
187192
execute: (args: Measurement[]) => {
188-
189193
args.forEach((m) => {
190194
if (!m.displayLabels) {
191195
m.displayLabels = true;
@@ -205,7 +209,6 @@ export class MeasurementActionDefinitions {
205209
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideLabels"),
206210
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideLabels"),
207211
execute: (args: Measurement[]) => {
208-
209212
args.forEach((m) => {
210213
if (m.displayLabels) {
211214
m.displayLabels = false;
@@ -225,7 +228,6 @@ export class MeasurementActionDefinitions {
225228
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurementAxes"),
226229
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurementAxes"),
227230
execute: (args: Measurement[]) => {
228-
229231
args.forEach((m) => {
230232
if (m instanceof DistanceMeasurement && !m.showAxes) {
231233
m.showAxes = true;
@@ -245,7 +247,6 @@ export class MeasurementActionDefinitions {
245247
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurementAxes"),
246248
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurementAxes"),
247249
execute: (args: Measurement[]) => {
248-
249250
args.forEach((m) => {
250251
if (m instanceof DistanceMeasurement && m.showAxes) {
251252
m.showAxes = false;
@@ -312,33 +313,53 @@ export class MeasurementActionToolbar {
312313
* @param measurements array of Measurement the actions are for. First one is always the one that initiated the event.
313314
* @param screenPoint Where on the screen the toolbar is to be positioned (e.g. cursor point)
314315
* @param offset Optional offset from the position. Default is (0,0)
315-
* @param relativePosition Optional direction the toolbar will open from. Default is Top (so will be above and centered from point + offset).
316316
* @returns true if the toolbar was opened, false if otherwise (e.g. no action items to view).
317317
*/
318-
public static openToolbar(measurements: Measurement[], screenPoint: XAndY, offset?: XAndY, relativePosition?: RelativePosition): boolean {
318+
public static openToolbar(measurements: Measurement[], screenPoint: XAndY, offset?: XAndY): boolean {
319319
// Ensure a previous toolbar was closed out
320320
this.closeToolbar(false);
321321

322322
const measurementsForActions = measurements.filter((m) => m.allowActions);
323-
if (measurementsForActions.length === 0)
324-
return false;
323+
if (measurementsForActions.length === 0) return false;
325324

326-
if (this._filterHandler && !this._filterHandler(measurementsForActions))
327-
return false;
325+
if (this._filterHandler && !this._filterHandler(measurementsForActions)) return false;
328326

329327
// Query all action items...if have none, do not show the toolbar
330328
const itemList = this.buildActionList(measurementsForActions);
331-
if (itemList.length === 0)
332-
return false;
329+
if (itemList.length === 0) return false;
333330

334331
// Build toolbar ID, making it unique so we can fade out a previous toolbar and not have that interfere with a new toolbar
335332
this._lastPopupId = `measurement-action-toolbar-${this._counter.toString()}`;
336333
this._counter++;
337334

338335
// Show toolbar
339-
const realOffset = (offset !== undefined) ? offset : Point2d.createZero();
340-
const realRelPosition = (relativePosition !== undefined) ? relativePosition : RelativePosition.Top;
341-
CursorPopupManager.open(this._lastPopupId, this.buildToolbar(measurementsForActions, itemList), screenPoint, realOffset, realRelPosition);
336+
const realOffset = offset !== undefined ? offset : Point2d.createZero();
337+
const parentDocument = IModelApp.viewManager.selectedView?.vpDiv.ownerDocument;
338+
if (!parentDocument) {
339+
return false;
340+
}
341+
342+
const toolItems: ToolbarActionItem[] = itemList.map((itemDef: MeasurementActionItemDef, index) => {
343+
itemDef.measurements = measurements;
344+
return ToolbarItemUtilities.createActionItem(itemDef.id, index * 10, itemDef.iconSpec, itemDef.label, itemDef.execute);
345+
});
346+
// Center the toolbar horizontally and position it to specified offset above the cursor point
347+
const toolbarWidth = toolItems.length * 36; // Approximate width based on typical button size
348+
const point = {
349+
x: screenPoint.x - realOffset.x - toolbarWidth / 2,
350+
y: screenPoint.y - realOffset.y,
351+
};
352+
const component = (
353+
<PositionPopup point={point}>
354+
<Toolbar expandsTo={Direction.Top} items={toolItems} />
355+
</PositionPopup>
356+
);
357+
PopupManager.addOrUpdatePopup({
358+
id: this._lastPopupId,
359+
pt: point,
360+
component,
361+
parentDocument,
362+
});
342363

343364
FeatureTracking.notifyFeature(MeasureToolsFeatures.MeasurementActionsToolbar_Open);
344365

@@ -351,17 +372,15 @@ export class MeasurementActionToolbar {
351372
public static closeToolbar(fadeOut: boolean = false): void {
352373
// Forcibly close a fading toolbar if we get another close call otherwise they may interfere with each other
353374
if (this._fadeoutPopId !== undefined) {
354-
CursorPopupManager.close(this._fadeoutPopId, false, false);
375+
PopupManager.removePopup(this._fadeoutPopId);
355376
this._fadeoutPopId = undefined;
356377
}
357378

358-
if (this._lastPopupId === undefined)
359-
return;
379+
if (this._lastPopupId === undefined) return;
360380

361-
if (fadeOut)
362-
this._fadeoutPopId = this._lastPopupId;
381+
if (fadeOut) this._fadeoutPopId = this._lastPopupId;
363382

364-
CursorPopupManager.close(this._lastPopupId, false, fadeOut);
383+
PopupManager.removePopup(this._lastPopupId);
365384
this._lastPopupId = undefined;
366385
}
367386

@@ -373,7 +392,7 @@ export class MeasurementActionToolbar {
373392
// Filter out duplicates
374393
this.dropActionProvider(provider);
375394

376-
const realPriority = (providerPriority !== undefined) ? providerPriority : 0;
395+
const realPriority = providerPriority !== undefined ? providerPriority : 0;
377396
this._actionProviders.push({ priority: realPriority, provider });
378397

379398
this._actionProviders.sort((a, b) => b.priority - a.priority);
@@ -384,8 +403,7 @@ export class MeasurementActionToolbar {
384403
*/
385404
public static dropActionProvider(provider: MeasurementActionProvider): void {
386405
for (let i = 0; i < this._actionProviders.length; i++) {
387-
if (this._actionProviders[i].provider === provider)
388-
this._actionProviders.splice(i, 1);
406+
if (this._actionProviders[i].provider === provider) this._actionProviders.splice(i, 1);
389407
}
390408
}
391409

@@ -397,31 +415,31 @@ export class MeasurementActionToolbar {
397415
/** Sets a default provider (which can be later cleared). This sets lock/unlock, delete, and open properties action buttons. */
398416
public static setDefaultActionProvider() {
399417
MeasurementActionToolbar.addActionProvider((measurements: Measurement[], actionItemList: MeasurementActionItemDef[]) => {
400-
401418
let allMeasurementsShowingLabels = true;
402419
let allMeasurementsLocked = true;
403420
let hasDistanceMeasurements = false;
404421
let allDistanceMeasurementsShowingAxes = true;
405422

406423
for (const measurement of measurements) {
407-
if (!measurement.isLocked)
408-
allMeasurementsLocked = false;
424+
if (!measurement.isLocked) allMeasurementsLocked = false;
409425

410-
if (!measurement.displayLabels)
411-
allMeasurementsShowingLabels = false;
426+
if (!measurement.displayLabels) allMeasurementsShowingLabels = false;
412427

413428
if (measurement instanceof DistanceMeasurement) {
414429
hasDistanceMeasurements = true;
415-
if (!measurement.showAxes)
416-
allDistanceMeasurementsShowingAxes = false;
430+
if (!measurement.showAxes) allDistanceMeasurementsShowingAxes = false;
417431
}
418432
}
419433

420-
actionItemList.push((allMeasurementsLocked) ? MeasurementActionDefinitions.unlockAction : MeasurementActionDefinitions.lockAction);
421-
actionItemList.push((allMeasurementsShowingLabels) ? MeasurementActionDefinitions.hideMeasurementLabels : MeasurementActionDefinitions.displayMeasurementLabels);
434+
actionItemList.push(allMeasurementsLocked ? MeasurementActionDefinitions.unlockAction : MeasurementActionDefinitions.lockAction);
435+
actionItemList.push(
436+
allMeasurementsShowingLabels ? MeasurementActionDefinitions.hideMeasurementLabels : MeasurementActionDefinitions.displayMeasurementLabels,
437+
);
422438

423439
if (hasDistanceMeasurements)
424-
actionItemList.push((allDistanceMeasurementsShowingAxes) ? MeasurementActionDefinitions.hideMeasurementAxes : MeasurementActionDefinitions.displayMeasurementAxes);
440+
actionItemList.push(
441+
allDistanceMeasurementsShowingAxes ? MeasurementActionDefinitions.hideMeasurementAxes : MeasurementActionDefinitions.displayMeasurementAxes,
442+
);
425443

426444
actionItemList.push(MeasurementActionDefinitions.deleteAction);
427445
actionItemList.push(MeasurementActionDefinitions.openPropertiesAction);
@@ -431,8 +449,7 @@ export class MeasurementActionToolbar {
431449
private static buildActionList(measurements: Measurement[]): MeasurementActionItemDef[] {
432450
const itemList = new Array<MeasurementActionItemDef>();
433451

434-
for (const entry of this._actionProviders)
435-
entry.provider(measurements, itemList);
452+
for (const entry of this._actionProviders) entry.provider(measurements, itemList);
436453

437454
return itemList;
438455
}
@@ -442,7 +459,7 @@ export class MeasurementActionToolbar {
442459
itemDef.measurements = measurements;
443460
return ToolbarItemUtilities.createActionItem(itemDef.id, index * 10, itemDef.iconSpec, itemDef.label, itemDef.execute);
444461
});
445-
return <PopupToolbar items={toolItems} onClose={() => MeasurementActionToolbar.closeToolbar(true)}/>;
462+
return <PopupToolbar items={toolItems} onClose={() => MeasurementActionToolbar.closeToolbar(true)} />;
446463
}
447464
}
448465

@@ -462,7 +479,7 @@ ShimFunctions.defaultButtonEventAction = (measurement: Measurement, pickContext:
462479
measurements.unshift(measurement);
463480

464481
// Open toolbar for measurement
465-
MeasurementActionToolbar.openToolbar(measurements, CursorInformation.cursorPosition, Point2d.create(0, 10));
482+
MeasurementActionToolbar.openToolbar(measurements, CursorInformation.cursorPosition, Point2d.create(0, 60));
466483

467484
// Notify global event that this measurement is responding to the button
468485
MeasurementManager.instance.notifyMeasurementButtonEvent(measurement, pickContext);

0 commit comments

Comments
 (0)