@itwin/core-frontend
Version:
iTwin.js frontend components
273 lines • 15.5 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Tools
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EditManipulator = void 0;
const core_geometry_1 = require("@itwin/core-geometry");
const AccuDraw_1 = require("../AccuDraw");
const IModelApp_1 = require("../IModelApp");
const Tool_1 = require("./Tool");
const ToolAdmin_1 = require("./ToolAdmin");
/** Classes and methods to create on screen control handles for interactive modification of element(s) and pickable decorations.
* The basic flow is:
* - Create a sub-class of [[EditManipulator.HandleProvider]] to listen for start of [[SelectTool]] or any other PrimitiveTool that supports handle providers.
* - Respond to [[ManipulatorToolEvent.Start]] by adding a listener for [[SelectionSet]] change event.
* - Respond to selection changed event to create control handles as pickable decorations when the desired element(s) or pickable decoration is selected.
* - Respond to button events on the control handle decoration and run a sub-class of [[EditManipulator.HandleTool]] to modify.
* @public
* @extensions
*/
var EditManipulator;
(function (EditManipulator) {
/** Specifies the event for [[EditManipulator.HandleProvider.onManipulatorEvent]] */
let EventType;
(function (EventType) {
/** Control handles should be created, updated, or cleared based on the active selection. */
EventType[EventType["Synch"] = 0] = "Synch";
/** Control handle modification was cancelled by user. */
EventType[EventType["Cancel"] = 1] = "Cancel";
/** Control handle modification was accepted by user. */
EventType[EventType["Accept"] = 2] = "Accept";
})(EventType = EditManipulator.EventType || (EditManipulator.EventType = {}));
/** Interactive control handle modification is done by installing an [[InputCollector]].
* Modification typically is started from a click or press and drag while over the handle graphics.
* The HandleTool base class is set up to define an offset by 2 points. The second point is
* defined by either another click, or up event when initiated from press and drag.
* @see [[EditManipulator.HandleProvider]]
*/
class HandleTool extends Tool_1.InputCollector {
static toolId = "Select.Manipulator"; // Doesn't matter, not included in tool registry...
static hidden = true;
manipulator;
constructor(manipulator) {
super();
this.manipulator = manipulator;
}
/** Establish the initial tool state for handle modification.
* Default implementation honors the active locks and enables AccuSnap; behavior suitable for a shape vertex handle.
* @note An InputCollector inherits the tool state of the suspended primitive tool.
*/
init() {
// Set this.receivedDownEvent to still get up events sent to this tool instance when installed from another tool's down event (ex. onModelStartDrag).
this.receivedDownEvent = true;
// Override inherited tool state from suspended primitive tool.
IModelApp_1.IModelApp.accuSnap.onStartTool();
if (this.wantAccuSnap)
this.initLocateElements(false, true, undefined, Tool_1.CoordinateLockOverrides.None);
else
this.initLocateElements(false, false, undefined, Tool_1.CoordinateLockOverrides.All);
}
/** Whether to call [[AccuSnap.enableSnap]] for handle modification.
* @return true to enable snapping to elements.
*/
get wantAccuSnap() { return true; }
/** Called from reset button up event to allow modification to be cancelled.
* @return true to cancel modification.
*/
cancel(_ev) { return true; }
/** Called following cancel or accept to update the handle provider
* and return control to suspended PrimitiveTool.
*/
async onComplete(_ev, event) {
await this.exitTool();
this.manipulator.onManipulatorEvent(event);
return Tool_1.EventHandled.Yes;
}
async onDataButtonDown(ev) {
if (!this.accept(ev))
return Tool_1.EventHandled.No;
return this.onComplete(ev, EventType.Accept);
}
async onResetButtonUp(ev) {
if (!this.cancel(ev))
return Tool_1.EventHandled.No;
return this.onComplete(ev, EventType.Cancel);
}
async onTouchMove(ev) { return IModelApp_1.IModelApp.toolAdmin.convertTouchMoveToMotion(ev); }
async onTouchComplete(ev) { return IModelApp_1.IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); }
async onTouchCancel(ev) { return IModelApp_1.IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, Tool_1.BeButton.Reset); }
async onPostInstall() {
await super.onPostInstall();
this.init();
}
}
EditManipulator.HandleTool = HandleTool;
/** A handle provider maintains a set of controls used to modify element(s) or pickable decorations.
* The provider works in conjunction with any PrimitiveTool that raises events for [[ToolAdmin.manipulatorToolEvent]].
* @see [[SelectTool]] The default PrimitiveTool that supports handle providers.
*/
class HandleProvider {
iModel;
_isActive = false;
_removeManipulatorToolListener;
_removeSelectionListener;
_removeDecorationListener;
/** Create a new handle provider to listen for [[ToolAdmin.manipulatorToolEvent]].
* Usually followed by a call to [[IModelApp.toolAdmin.startDefaultTool]] to immediately raise the [[ManipulatorToolEvent.Start]] event.
*/
constructor(iModel) {
this.iModel = iModel;
this._removeManipulatorToolListener = IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.addListener((tool, event) => this.onManipulatorToolEvent(tool, event));
}
/** Call to clear this handle provider. */
stop() {
if (this._removeSelectionListener) {
this._removeSelectionListener();
this._removeSelectionListener = undefined;
}
if (this._removeManipulatorToolListener) {
this._removeManipulatorToolListener();
this._removeManipulatorToolListener = undefined;
}
this.clearControls();
}
/** Event raised by a PrimitiveTool that supports handle providers.
* Add listener for [[IModelConnection.selectionSet.onChanged]] on start event and remove on stop event.
* Control handles can be created from the active selection set, which may include persistent elements and pickable decorations.
* @see [[SelectionSet]]
*/
onManipulatorToolEvent(_tool, event) {
switch (event) {
case ToolAdmin_1.ManipulatorToolEvent.Start: {
if (this._removeSelectionListener)
break;
this._removeSelectionListener = this.iModel.selectionSet.onChanged.addListener((ev) => this.onSelectionChanged(ev));
if (this.iModel.selectionSet.isActive)
this.onManipulatorEvent(EventType.Synch); // Give opportunity to add controls when tool is started with an existing selection...
break;
}
case ToolAdmin_1.ManipulatorToolEvent.Stop: {
if (!this._removeSelectionListener)
break;
this._removeSelectionListener();
this._removeSelectionListener = undefined;
this.clearControls();
}
}
}
/** Event raised by [[SelectionSet]] when the active selection changes.
* Calls onManipulatorEvent to let the provider create, update, or clear it's set of controls as appropriate.
* @see [[SelectionSet]]
*/
onSelectionChanged(ev) {
if (this.iModel === ev.set.iModel)
this.onManipulatorEvent(EventType.Synch);
}
/** Register for decorate event to start displaying control handles. */
updateDecorationListener(add) {
if (this._removeDecorationListener) {
if (!add) {
this._removeDecorationListener();
this._removeDecorationListener = undefined;
}
IModelApp_1.IModelApp.viewManager.invalidateDecorationsAllViews();
}
else if (add) {
if (!this._removeDecorationListener)
this._removeDecorationListener = IModelApp_1.IModelApp.viewManager.addDecorator(this);
IModelApp_1.IModelApp.viewManager.invalidateDecorationsAllViews();
}
}
/** Sub-classes should override to display the pickable graphics for their controls. */
decorate(_context) { }
/* Call to stop displaying the the control handles */
clearControls() {
this.updateDecorationListener(this._isActive = false);
}
/* Create, update, or clear based on the current selection. */
async updateControls() {
const created = await this.createControls();
if (this._isActive && !created)
this.clearControls();
else
this.updateDecorationListener(this._isActive = created);
}
/* Update controls to reflect active selection or post-modification state. */
onManipulatorEvent(_eventType) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.updateControls();
}
/** Sub-classes can override to perform some operation for a double click on a handle. */
async onDoubleClick(_hit, _ev) { return Tool_1.EventHandled.No; }
/** Sub-classes can override to present a menu for a right click on a handle. */
async onRightClick(_hit, _ev) { return Tool_1.EventHandled.No; }
/** Sub-classes can override to respond to a touch tap on a handle. By default, handles are selected by touch drag and taps are ignored. */
async onTouchTap(_hit, _ev) { return Tool_1.EventHandled.Yes; }
/** Event raised by a PrimitiveTool that supports handle providers to allow a pickable decoration to respond to being located. */
async onDecorationButtonEvent(hit, ev) {
if (!this._isActive)
return Tool_1.EventHandled.No;
if (ev.isDoubleClick)
return this.onDoubleClick(hit, ev); // Allow double click on handle to override default operation (ex. fit view).
if (Tool_1.BeButton.Reset === ev.button && !ev.isDown && !ev.isDragging)
return this.onRightClick(hit, ev); // Allow right click on handle to present a menu.
if (Tool_1.BeButton.Data !== ev.button)
return Tool_1.EventHandled.No;
if (ev.isControlKey)
return Tool_1.EventHandled.No; // Support ctrl+click to select multiple controls (ex. linestring vertices)...
if (Tool_1.InputSource.Touch === ev.inputSource && !ev.isDragging)
return this.onTouchTap(hit, ev); // Default is to select controls on touch drag only, ignore tap on control...
if (ev.isDown && !ev.isDragging)
return Tool_1.EventHandled.No; // Select controls on up event or down event only after drag started...
if (!await this.modifyControls(hit, ev))
return Tool_1.EventHandled.No;
// In case InputCollector was installed for handle modification, don't wait for motion to show dynamic frame adjusted for AccuDraw hints...
IModelApp_1.IModelApp.accuDraw.refreshDecorationsAndDynamics();
return Tool_1.EventHandled.Yes;
}
}
EditManipulator.HandleProvider = HandleProvider;
/** Utility methods for creating control handles and other decorations. */
class HandleUtils {
/** Adjust input color for contrast against view background.
* @param color The color to adjust.
* @param vp The viewport to compare.
* @return color adjusted for view background color or original color if view background color isn't being used.
*/
static adjustForBackgroundColor(color, vp) {
if (vp.view.is3d() && vp.view.getDisplayStyle3d().environment.displaySky)
return color;
return color.adjustedForContrast(vp.view.backgroundColor);
}
/** Compute a transform that will try to orient a 2d shape (like an arrow) to face the camera.
* @param vp The viewport to get the rotation from.
* @param base The world coordinate point to pivot about.
* @param direction The world coordinate axis to tilt along.
* @param sizeInches The transform scale specified in screen inches.
* @returns transform or undefined when input direction is almost perpendicular to viewing direction.
* @see [[getArrowShape]]
*/
static getArrowTransform(vp, base, direction, sizeInches) {
const boresite = AccuDraw_1.AccuDrawHintBuilder.getBoresite(base, vp);
if (Math.abs(direction.dotProduct(boresite.direction)) >= 0.99)
return undefined;
const pixelSize = vp.pixelsFromInches(sizeInches);
const scale = vp.viewingSpace.getPixelSizeAtPoint(base) * pixelSize;
const matrix = core_geometry_1.Matrix3d.createRigidFromColumns(direction, boresite.direction, core_geometry_1.AxisOrder.XZY);
if (undefined === matrix)
return undefined;
matrix.scaleColumnsInPlace(scale, scale, scale);
return core_geometry_1.Transform.createRefs(base.clone(), matrix);
}
/** Return array of shape points representing a unit arrow in xy plane pointing in positive x direction. */
static getArrowShape(baseStart = 0.0, baseWidth = 0.15, tipStart = 0.55, tipEnd = 1.0, tipWidth = 0.3, flangeStart = tipStart, flangeWidth = baseWidth) {
const shapePts = [];
shapePts[0] = core_geometry_1.Point3d.create(tipEnd, 0.0);
shapePts[1] = core_geometry_1.Point3d.create(flangeStart, tipWidth);
shapePts[2] = core_geometry_1.Point3d.create(tipStart, flangeWidth);
shapePts[3] = core_geometry_1.Point3d.create(baseStart, baseWidth);
shapePts[4] = core_geometry_1.Point3d.create(baseStart, -baseWidth);
shapePts[5] = core_geometry_1.Point3d.create(tipStart, -flangeWidth);
shapePts[6] = core_geometry_1.Point3d.create(flangeStart, -tipWidth);
shapePts[7] = shapePts[0].clone();
return shapePts;
}
}
EditManipulator.HandleUtils = HandleUtils;
})(EditManipulator || (exports.EditManipulator = EditManipulator = {}));
//# sourceMappingURL=EditManipulator.js.map