@itwin/measure-tools-react
Version:
Frontend framework and tools for measurements
356 lines • 18.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from "react";
import { Point2d } from "@itwin/core-geometry";
import { RelativePosition } from "@itwin/appui-abstract";
import { ActionButtonItemDef, CursorInformation, CursorPopupManager, ToolbarItemUtilities } from "@itwin/appui-react";
import { FeatureTracking, MeasureToolsFeatures } from "../api/FeatureTracking.js";
import { MeasurementManager } from "../api/MeasurementManager.js";
import { MeasurementSelectionSet } from "../api/MeasurementSelectionSet.js";
import { MeasurementUIEvents } from "../api/MeasurementUIEvents.js";
import { ShimFunctions } from "../api/ShimFunctions.js";
import { DistanceMeasurement } from "../measurements/DistanceMeasurement.js";
import { PopupToolbar } from "./PopupToolbar.js";
import { MeasureTools } from "../MeasureTools.js";
/** Toolbar button action item. Encapsulates all the properties to display a button in the toolbar and execute some logic when pressed. */
export class MeasurementActionItemDef extends ActionButtonItemDef {
/** Constructs a new measurement action item definition. */
constructor(props) {
super(props);
this._id = props.id;
this._commandHandler = { execute: props.execute };
}
/** Gets the non-localized id to identify the item */
get id() {
return this._id;
}
/** Gets the measurement that the toolbar was opened for. */
get measurements() {
return this._commandHandler.parameters;
}
/** Sets the measurement. */
set measurements(measurements) {
this._commandHandler.parameters = measurements;
}
/** Executes user callback and closes toolbar. */
execute() {
super.execute();
MeasurementActionToolbar.closeToolbar(false);
}
}
/** Common measurement action items. */
export class MeasurementActionDefinitions {
/** Creates a new delete action item. This drops the measurement from the decorator. */
static get deleteAction() {
return new MeasurementActionItemDef({
id: "delete",
iconSpec: "icon-measure-clear",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Measurements.clearMeasurement"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Measurements.clearMeasurement"),
execute: (arg) => {
MeasurementManager.instance.dropMeasurement(arg);
FeatureTracking.notifyFeature(MeasureToolsFeatures.MeasurementActions_Delete);
},
});
}
/** Creates a new open properties action item. This opens the property grid to display measurement properties. */
static get openPropertiesAction() {
return new MeasurementActionItemDef({
id: "open-properties",
iconSpec: "icon-properties",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.properties"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.properties"),
execute: (args) => {
MeasurementSelectionSet.global.add(args);
},
});
}
/** Creates a new unlock action item. This sets the isLocked property to false and causes the meaurement to be redrawn. */
static get unlockAction() {
return new MeasurementActionItemDef({
id: "unlock",
iconSpec: "icon-lock-unlocked",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.unlock"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.unlock"),
execute: (args) => {
args.forEach((m) => {
if (m.isLocked) {
m.isLocked = false;
m.viewTarget.invalidateViewportDecorations();
}
});
MeasurementUIEvents.notifyMeasurementsChanged(); // By default locking influences visibility of the UI buttons
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_Lock, false);
},
});
}
/** Creates a new lock action item. This sets the isLocked property to true and causes the measurement to be redrawn. */
static get lockAction() {
return new MeasurementActionItemDef({
id: "lock",
iconSpec: "icon-lock",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.lock"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.lock"),
execute: (args) => {
args.forEach((m) => {
if (!m.isLocked) {
m.isLocked = true;
m.viewTarget.invalidateViewportDecorations();
}
});
MeasurementUIEvents.notifyMeasurementsChanged(); // By default locking influences visibility of the UI buttons
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_Lock, true);
},
});
}
static get hideMeasurements() {
return new MeasurementActionItemDef({
id: "hide-measurements",
iconSpec: "icon-visibility-hide",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurements"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeaurements"),
execute: (args) => {
args.forEach((m) => {
if (m.isVisible) {
m.isVisible = false;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayMeasurements, false);
},
});
}
static get displayMeasurements() {
return new MeasurementActionItemDef({
id: "display-measurements",
iconSpec: "icon-visibility",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurements"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurements"),
execute: (args) => {
args.forEach((m) => {
if (!m.isVisible) {
m.isVisible = true;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayMeasurements, true);
},
});
}
static get displayMeasurementLabels() {
return new MeasurementActionItemDef({
id: "display-labels",
iconSpec: "icon-layers",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayLabels"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayLabels"),
execute: (args) => {
args.forEach((m) => {
if (!m.displayLabels) {
m.displayLabels = true;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayLabels, true);
},
});
}
static get hideMeasurementLabels() {
return new MeasurementActionItemDef({
id: "hide-labels",
iconSpec: "icon-layers-hide",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideLabels"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideLabels"),
execute: (args) => {
args.forEach((m) => {
if (m.displayLabels) {
m.displayLabels = false;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayLabels, false);
},
});
}
static get displayMeasurementAxes() {
return new MeasurementActionItemDef({
id: "display-rise-run",
iconSpec: "icon-measure-2d-show",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurementAxes"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.displayMeasurementAxes"),
execute: (args) => {
args.forEach((m) => {
if (m instanceof DistanceMeasurement && !m.showAxes) {
m.showAxes = true;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayAxes, true);
},
});
}
static get hideMeasurementAxes() {
return new MeasurementActionItemDef({
id: "hide-rise-run",
iconSpec: "icon-measure-2d-hide",
label: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurementAxes"),
tooltip: () => MeasureTools.localization.getLocalizedString("MeasureTools:Generic.hideMeasurementAxes"),
execute: (args) => {
args.forEach((m) => {
if (m instanceof DistanceMeasurement && m.showAxes) {
m.showAxes = false;
m.viewTarget.invalidateViewportDecorations();
}
});
FeatureTracking.notifyToggledFeature(MeasureToolsFeatures.MeasurementActions_ToggleDisplayAxes, false);
},
});
}
}
/** Manager for an action toolbar for measurements. The measurement decorator will respond to pick button events and open the toolbar. */
export class MeasurementActionToolbar {
/** Gets if the toolbar is currently opened. */
static get isOpened() {
return this._lastPopupId !== undefined;
}
/** Gets the filter handler if one was set. If a measurement is rejected by the filter, the toolbar will NOT open for it. */
static get filterHandler() {
return this._filterHandler;
}
/** Sets the filter handler. If a measurement is rejected by the filter, the toolbar will NOT open for it. */
static set filterHandler(handler) {
this._filterHandler = handler;
}
/** Gets a readonly list of currently registered action providers. */
static get actionProviders() {
return this._actionProviders;
}
/** Opens the measurement toolbar for a list of measurements.
* @param measurements array of Measurement the actions are for. First one is always the one that initiated the event.
* @param screenPoint Where on the screen the toolbar is to be positioned (e.g. cursor point)
* @param offset Optional offset from the position. Default is (0,0)
* @param relativePosition Optional direction the toolbar will open from. Default is Top (so will be above and centered from point + offset).
* @returns true if the toolbar was opened, false if otherwise (e.g. no action items to view).
*/
static openToolbar(measurements, screenPoint, offset, relativePosition) {
// Ensure a previous toolbar was closed out
this.closeToolbar(false);
const measurementsForActions = measurements.filter((m) => m.allowActions);
if (measurementsForActions.length === 0)
return false;
if (this._filterHandler && !this._filterHandler(measurementsForActions))
return false;
// Query all action items...if have none, do not show the toolbar
const itemList = this.buildActionList(measurementsForActions);
if (itemList.length === 0)
return false;
// Build toolbar ID, making it unique so we can fade out a previous toolbar and not have that interfere with a new toolbar
this._lastPopupId = `measurement-action-toolbar-${this._counter.toString()}`;
this._counter++;
// Show toolbar
const realOffset = (offset !== undefined) ? offset : Point2d.createZero();
const realRelPosition = (relativePosition !== undefined) ? relativePosition : RelativePosition.Top;
CursorPopupManager.open(this._lastPopupId, this.buildToolbar(measurementsForActions, itemList), screenPoint, realOffset, realRelPosition);
FeatureTracking.notifyFeature(MeasureToolsFeatures.MeasurementActionsToolbar_Open);
return true;
}
/** Closes the measurement toolbar.
* @param fadeOut Optionally animate the toolbar fading out. Default is false.
*/
static closeToolbar(fadeOut = false) {
// Forcibly close a fading toolbar if we get another close call otherwise they may interfere with each other
if (this._fadeoutPopId !== undefined) {
CursorPopupManager.close(this._fadeoutPopId, false, false);
this._fadeoutPopId = undefined;
}
if (this._lastPopupId === undefined)
return;
if (fadeOut)
this._fadeoutPopId = this._lastPopupId;
CursorPopupManager.close(this._lastPopupId, false, fadeOut);
this._lastPopupId = undefined;
}
/** Adds an action item provider. Action providers have a priority, and the list is sorted from highest priority to lowest.
* @param provider The action provider to add.
* @param providerPriority Optionally provide a sort priority (higher values are called first)
*/
static addActionProvider(provider, providerPriority) {
// Filter out duplicates
this.dropActionProvider(provider);
const realPriority = (providerPriority !== undefined) ? providerPriority : 0;
this._actionProviders.push({ priority: realPriority, provider });
this._actionProviders.sort((a, b) => b.priority - a.priority);
}
/** Remove a specific action item provider. If all are removed, the toolbar will not open.
* @param provider The action provider to drop.
*/
static dropActionProvider(provider) {
for (let i = 0; i < this._actionProviders.length; i++) {
if (this._actionProviders[i].provider === provider)
this._actionProviders.splice(i, 1);
}
}
/** Remove all action item providers. */
static clearActionProviders() {
this._actionProviders = [];
}
/** Sets a default provider (which can be later cleared). This sets lock/unlock, delete, and open properties action buttons. */
static setDefaultActionProvider() {
MeasurementActionToolbar.addActionProvider((measurements, actionItemList) => {
let allMeasurementsShowingLabels = true;
let allMeasurementsLocked = true;
let hasDistanceMeasurements = false;
let allDistanceMeasurementsShowingAxes = true;
for (const measurement of measurements) {
if (!measurement.isLocked)
allMeasurementsLocked = false;
if (!measurement.displayLabels)
allMeasurementsShowingLabels = false;
if (measurement instanceof DistanceMeasurement) {
hasDistanceMeasurements = true;
if (!measurement.showAxes)
allDistanceMeasurementsShowingAxes = false;
}
}
actionItemList.push((allMeasurementsLocked) ? MeasurementActionDefinitions.unlockAction : MeasurementActionDefinitions.lockAction);
actionItemList.push((allMeasurementsShowingLabels) ? MeasurementActionDefinitions.hideMeasurementLabels : MeasurementActionDefinitions.displayMeasurementLabels);
if (hasDistanceMeasurements)
actionItemList.push((allDistanceMeasurementsShowingAxes) ? MeasurementActionDefinitions.hideMeasurementAxes : MeasurementActionDefinitions.displayMeasurementAxes);
actionItemList.push(MeasurementActionDefinitions.deleteAction);
actionItemList.push(MeasurementActionDefinitions.openPropertiesAction);
});
}
static buildActionList(measurements) {
const itemList = new Array();
for (const entry of this._actionProviders)
entry.provider(measurements, itemList);
return itemList;
}
static buildToolbar(measurements, actionItemList) {
const toolItems = actionItemList.map((itemDef, index) => {
itemDef.measurements = measurements;
return ToolbarItemUtilities.createActionItem(itemDef.id, index * 10, itemDef.iconSpec, itemDef.label, itemDef.execute);
});
return React.createElement(PopupToolbar, { items: toolItems, onClose: () => MeasurementActionToolbar.closeToolbar(true) });
}
}
MeasurementActionToolbar._actionProviders = [];
MeasurementActionToolbar._counter = 0;
// Make sure the toolbar closes if there are any programmatic changes to measurements
MeasurementUIEvents.onMeasurementsChanged.addListener(() => MeasurementActionToolbar.closeToolbar(false));
// Setup action toolbar open function so measurements respond properly in their button event (if referenced directly webpack gives a circular dependency issue)
ShimFunctions.defaultButtonEventAction = (measurement, pickContext) => {
const measurements = MeasurementSelectionSet.global.measurements;
// If the measurement that initiated this event is in that list, remove it.
const index = measurements.indexOf(measurement);
if (-1 !== index)
measurements.splice(index, 1);
// The measurement that initiated this event always comes first
measurements.unshift(measurement);
// Open toolbar for measurement
MeasurementActionToolbar.openToolbar(measurements, CursorInformation.cursorPosition, Point2d.create(0, 10));
// Notify global event that this measurement is responding to the button
MeasurementManager.instance.notifyMeasurementButtonEvent(measurement, pickContext);
};
//# sourceMappingURL=MeasurementActionToolbar.js.map