UNPKG

@itwin/core-frontend

Version:
273 lines • 15.5 kB
"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