"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