diff --git a/react-components/package.json b/react-components/package.json index 0a39f20b98b..19edba71429 100644 --- a/react-components/package.json +++ b/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@cognite/reveal-react-components", - "version": "0.46.2", + "version": "0.47.1", "exports": { ".": { "import": "./dist/index.js", @@ -30,7 +30,7 @@ }, "peerDependencies": { "@cognite/cogs.js": ">=9", - "@cognite/reveal": "4.14.3", + "@cognite/reveal": "4.14.5", "react": ">=18", "react-dom": ">=18", "styled-components": ">=5" @@ -44,7 +44,7 @@ "@cognite/cdf-i18n-utils": "^0.7.5", "@cognite/cdf-utilities": "^3.6.0", "@cognite/cogs.js": "^9.3.0", - "@cognite/reveal": "^4.14.3", + "@cognite/reveal": "^4.14.5", "@cognite/sdk": "^9.13.0", "@playwright/test": "^1.43.1", "@storybook/addon-essentials": "^8.0.9", diff --git a/react-components/src/architecture/base/commands/BaseEditTool.ts b/react-components/src/architecture/base/commands/BaseEditTool.ts index afb6f425d98..5cdec1a3945 100644 --- a/react-components/src/architecture/base/commands/BaseEditTool.ts +++ b/react-components/src/architecture/base/commands/BaseEditTool.ts @@ -6,8 +6,8 @@ import { NavigationTool } from './NavigationTool'; import { type DomainObject } from '../domainObjects/DomainObject'; import { isDomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection'; import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger'; -import { type VisualDomainObject } from '../domainObjects/VisualDomainObject'; -import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; +import { VisualDomainObject } from '../domainObjects/VisualDomainObject'; +import { type AnyIntersection, CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; /** * The `BaseEditTool` class is an abstract class that extends the `NavigationTool` class. @@ -65,10 +65,25 @@ export abstract class BaseEditTool extends NavigationTool { } } + public override onDeactivate(): void { + super.onDeactivate(); + this.deselectAll(); + } + // ================================================== // VIRTUAL METHODS // ================================================== + /** + * Determines whether the specified domain object can be selected or dragged by this edit tool. + * + * @param _domainObject - The domain object to be accepted. + * @returns `true` if the domain object can be accepted, `false` otherwise. + */ + protected canBeSelected(_domainObject: DomainObject): boolean { + return false; + } + /** * Override this function to create custom dragger * with other creation logic. Otherwise createDragger in @@ -82,7 +97,7 @@ export abstract class BaseEditTool extends NavigationTool { if (!isDomainObjectIntersection(intersection)) { return undefined; } - const domainObject = intersection.domainObject as VisualDomainObject; + const domainObject = this.getIntersectedDomainObject(intersection); if (domainObject === undefined) { return undefined; } @@ -98,13 +113,31 @@ export abstract class BaseEditTool extends NavigationTool { // INSTANCE METHODS // ================================================== - protected deselectAll(except?: DomainObject | undefined): void { + protected deselectAll(except?: VisualDomainObject | undefined): void { const { rootDomainObject } = this; for (const domainObject of rootDomainObject.getDescendants()) { + if (!this.canBeSelected(domainObject)) { + continue; + } if (except !== undefined && domainObject === except) { continue; } domainObject.setSelectedInteractive(false); } } + + protected getIntersectedDomainObject( + intersection: AnyIntersection | undefined + ): VisualDomainObject | undefined { + if (!isDomainObjectIntersection(intersection)) { + return undefined; + } + if (!this.canBeSelected(intersection.domainObject)) { + return undefined; + } else if (intersection.domainObject instanceof VisualDomainObject) { + return intersection.domainObject; + } else { + return undefined; + } + } } diff --git a/react-components/src/architecture/base/commands/BaseTool.ts b/react-components/src/architecture/base/commands/BaseTool.ts index cf09e2cb230..6e5aced0ce0 100644 --- a/react-components/src/architecture/base/commands/BaseTool.ts +++ b/react-components/src/architecture/base/commands/BaseTool.ts @@ -35,14 +35,14 @@ export abstract class BaseTool extends RenderTargetCommand { } public override get isChecked(): boolean { - return this.renderTarget.toolController.activeTool === this; + return this.renderTarget.commandsController.activeTool === this; } protected override invokeCore(): boolean { if (this.isChecked) { - this.renderTarget.toolController.activateDefaultTool(); + this.renderTarget.commandsController.activateDefaultTool(); } else { - this.renderTarget.toolController.setActiveTool(this); + this.renderTarget.commandsController.setActiveTool(this); } return true; } diff --git a/react-components/src/architecture/base/commands/RenderTargetCommand.ts b/react-components/src/architecture/base/commands/RenderTargetCommand.ts index d297277be1a..89a673a71b4 100644 --- a/react-components/src/architecture/base/commands/RenderTargetCommand.ts +++ b/react-components/src/architecture/base/commands/RenderTargetCommand.ts @@ -5,6 +5,7 @@ import { BaseCommand } from './BaseCommand'; import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; import { type RootDomainObject } from '../domainObjects/RootDomainObject'; +import { CommandsUpdater } from '../reactUpdaters/CommandsUpdater'; /** * Represents a base class where the render target is known. @@ -27,7 +28,7 @@ export abstract class RenderTargetCommand extends BaseCommand { public override invoke(): boolean { const success = this.invokeCore(); if (success) { - this.renderTarget.toolController.update(); + CommandsUpdater.update(this._renderTarget); } return success; } diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index 591d554faf5..91c47deba95 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -18,6 +18,8 @@ import { Views } from '../domainObjectsHelpers/Views'; import { type PanelInfo } from '../domainObjectsHelpers/PanelInfo'; import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; import { RootDomainObject } from './RootDomainObject'; +import { CommandsUpdater } from '../reactUpdaters/CommandsUpdater'; +import { DomainObjectPanelUpdater } from '../reactUpdaters/DomainObjectPanelUpdater'; /** * Represents an abstract base class for domain objects. @@ -226,7 +228,7 @@ export abstract class DomainObject { return true; // to be overridden } - public canBeChecked(_target: RevealRenderTarget): boolean { + public canBeSetVisibleNow(_target: RevealRenderTarget): boolean { return true; // to be overridden } @@ -244,27 +246,32 @@ export abstract class DomainObject { protected notifyCore(change: DomainObjectChange): void { this.views.notify(this, change); - // This is a little bit dirty, but will be refacored by using onIdle() if ( change.isChanged( Changes.visibleState, Changes.active, - Changes.active, Changes.selected, Changes.childAdded, Changes.childDeleted ) ) { if (this.root instanceof RootDomainObject) { - this.root.renderTarget.toolController.update(); + CommandsUpdater.update(this.root.renderTarget); } } + if (this.hasPanelInfo) { + DomainObjectPanelUpdater.notify(this, change); + } } // ================================================== // VIRTUAL METHODS: For updating the panel // ================================================== + public get hasPanelInfo(): boolean { + return false; // to be overridden + } + public getPanelInfo(): PanelInfo | undefined { return undefined; // to be overridden } @@ -341,7 +348,10 @@ export abstract class DomainObject { numCandidates++; if (childState === VisibleState.All) { numAll++; - } else if (childState === VisibleState.None || childState === VisibleState.CanNotBeChecked) { + } else if ( + childState === VisibleState.None || + childState === VisibleState.CanNotBeVisibleNow + ) { numNone++; } if (numNone < numCandidates && numCandidates < numAll) { @@ -355,7 +365,9 @@ export abstract class DomainObject { return VisibleState.All; } if (numCandidates === numNone) { - return this.canBeChecked(renderTarget) ? VisibleState.None : VisibleState.CanNotBeChecked; + return this.canBeSetVisibleNow(renderTarget) + ? VisibleState.None + : VisibleState.CanNotBeVisibleNow; } return VisibleState.Some; } @@ -369,7 +381,7 @@ export abstract class DomainObject { if (visibleState === VisibleState.Disabled) { return false; } - if (visibleState === VisibleState.None && !this.canBeChecked(renderTarget)) { + if (visibleState === VisibleState.None && !this.canBeSetVisibleNow(renderTarget)) { return false; } let hasChanged = false; @@ -753,7 +765,7 @@ export abstract class DomainObject { // ================================================== // INSTANCE METHODS: Color type - // Used in the renderstyle to determin which of the color a doamin object should have. + // Used in the renderstyle to determin which of the color a domain object should have. // ================================================== public supportsColorType(colorType: ColorType, solid: boolean): boolean { diff --git a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts index 29ba7a64eea..720e0da4006 100644 --- a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts @@ -25,10 +25,10 @@ export abstract class VisualDomainObject extends DomainObject { return VisibleState.All; } if (this.canCreateThreeView()) { - if (this.canBeChecked(renderTarget)) { + if (this.canBeSetVisibleNow(renderTarget)) { return VisibleState.None; } - return VisibleState.CanNotBeChecked; + return VisibleState.CanNotBeVisibleNow; } return VisibleState.Disabled; } @@ -38,7 +38,7 @@ export abstract class VisualDomainObject extends DomainObject { renderTarget: RevealRenderTarget, topLevel = true ): boolean { - if (visible && !this.canBeChecked(renderTarget)) { + if (visible && !this.canBeSetVisibleNow(renderTarget)) { return false; } if (!this.setVisible(visible, renderTarget)) { diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts index b9f88196d92..b92d6c373cd 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts @@ -3,27 +3,31 @@ */ import { Ray, type Vector3 } from 'three'; -import { type DomainObject } from '../domainObjects/DomainObject'; -import { type CreateDraggerProps } from '../domainObjects/VisualDomainObject'; +import { + type VisualDomainObject, + type CreateDraggerProps +} from '../domainObjects/VisualDomainObject'; /** * The `BaseDragger` class represents a utility for dragging and manipulating any object in 3D space. * It provides methods for onPointerDown, onPointerDrag, and onPointerUp based on user interactions. */ + export abstract class BaseDragger { // ================================================== // INSTANCE FIELDS // ================================================== - protected readonly point: Vector3; // Intersection point at pointer down in CDF coordinates protected readonly ray: Ray = new Ray(); // Intersection point at pointer down in CDF coordinates // ================================================== // CONTRUCTOR // ================================================== - + /** + * Represents a base dragger object. + * @param props - Contains the ray amd the clicked point in CDF coordinates + */ protected constructor(props: CreateDraggerProps) { - // Note: that yje point and the ray comes in CDF coordinates this.point = props.point; this.ray = props.ray; } @@ -32,17 +36,28 @@ export abstract class BaseDragger { // VIRTUAL METHODS // ================================================== - public abstract get domainObject(): DomainObject; - - public onPointerDown(_event: PointerEvent): void { - // Empty, probably not needed - } - - // This must be overriden - // Note: that the ray comes in CDF coordinates - public abstract onPointerDrag(_event: PointerEvent, ray: Ray): boolean; - - public onPointerUp(_event: PointerEvent): void { - // Empty, probably not needed - } + /** + * Returns the domain object to be manipulated + */ + public abstract get domainObject(): VisualDomainObject; + + /** + * Called just after the dragger is created and the pointer is down. + * @param _event - The pointer event object. + */ + public onPointerDown(_event: PointerEvent): void {} + + /** + * Called every times the mouse moves during dragging + * @param event - The pointer event. + * @param ray - The current ray in CDF cooordinates + * @returns True if the dragger has changed the domain object, false otherwise. + */ + public abstract onPointerDrag(event: PointerEvent, ray: Ray): boolean; + + /** + * Called just before the dragger is deleted. + * @param _event - The pointer event. + */ + public onPointerUp(_event: PointerEvent): void {} } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts b/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts index 2f3a2fcdb73..8e2c13f37d1 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts @@ -14,18 +14,15 @@ type PopupProps = { }; export class PopupStyle { - private readonly _horizontal: boolean = true; private readonly _left?: number = undefined; private readonly _right?: number = undefined; private readonly _top?: number = undefined; private readonly _bottom?: number = undefined; - private readonly _margin: number = 16; // margin ouside the popup - private readonly _padding: number = 16; // margin inside the popup + private readonly _margin: number = 16; // margin outside the popup + private readonly _padding: number = 8; // margin inside the popup + private readonly _horizontal: boolean = true; // Used for toolbars only public constructor(props: PopupProps) { - if (props.horizontal !== undefined) { - this._horizontal = props.horizontal; - } this._left = props.left; this._right = props.right; this._top = props.top; @@ -36,14 +33,9 @@ export class PopupStyle { if (props.padding !== undefined) { this._padding = props.padding; } - } - - public get flexFlow(): string { - return this._horizontal ? 'row' : 'column'; - } - - public get isDividerHorizontal(): boolean { - return !this._horizontal; + if (props.horizontal !== undefined) { + this._horizontal = props.horizontal; + } } public get leftPx(): string { @@ -73,4 +65,16 @@ export class PopupStyle { public static getStringWithPx(value?: number): string { return value === undefined ? 'undefined' : value.toString() + 'px'; } + + public get flexFlow(): string { + return this._horizontal ? 'row' : 'column'; + } + + public get isHorizontal(): boolean { + return this._horizontal; + } + + public get isHorizontalDivider(): boolean { + return !this._horizontal; + } } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts b/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts index 140d105f979..05ea53712b3 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts @@ -6,6 +6,6 @@ export enum VisibleState { All, // Visible Some, // Partly visible None, // None visible - CanNotBeChecked, // Can not be checked on, but it can be visible + CanNotBeVisibleNow, // Can not be checked on, but it can be visible Disabled // Visiable disabled } diff --git a/react-components/src/architecture/base/reactUpdaters/ActiveToolUpdater.ts b/react-components/src/architecture/base/reactUpdaters/ActiveToolUpdater.ts index 0348d513178..01ec6f3d1bf 100644 --- a/react-components/src/architecture/base/reactUpdaters/ActiveToolUpdater.ts +++ b/react-components/src/architecture/base/reactUpdaters/ActiveToolUpdater.ts @@ -24,7 +24,7 @@ export class ActiveToolUpdater { // Increment the counter, so the state change in React and force a redraw each time the active tool changes // The reason for solution it that I only want to store the active tool at one single location, since this gives a more // stabel code, and never goes out of sync. - // React get the active tool by: renderTarget.toolController.activeTool; + // React get the active tool by: renderTarget.commandsController.activeTool; if (this._setCounter === undefined) { return; } diff --git a/react-components/src/architecture/base/reactUpdaters/CommandsUpdater.ts b/react-components/src/architecture/base/reactUpdaters/CommandsUpdater.ts new file mode 100644 index 00000000000..ebee682c619 --- /dev/null +++ b/react-components/src/architecture/base/reactUpdaters/CommandsUpdater.ts @@ -0,0 +1,71 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; + +// By calling update(), it forces the updating of all the command and tools to be done only once for +// each user interaction, since it is done the first time the systems goes into onIdle state. +// It is only updating the commands and tools you see in the toolbars. + +// The update() will typically be called when click a button, delete or add an object, +// set object selected or visible. This will be done by the framework itself, but it may +// be cases where you have to do it manually. Then call: +// +// CommandsUpdater.update(renderTarget); + +export class CommandsUpdater { + private static _idleCallbackId: number = -1; + private static _renderTarget: RevealRenderTarget | undefined = undefined; + + public static update(renderTarget: RevealRenderTarget | undefined): void { + if (renderTarget === undefined) { + return; // If this is not given, we do not know which target to update + } + if (this._idleCallbackId >= 0) { + return; // Already requested + } + this._idleCallbackId = window.requestIdleCallback(this.onIdle); + this._renderTarget = renderTarget; + } + + public static dispose(): void { + this._renderTarget = undefined; + if (this._idleCallbackId < 0) { + return; + } + window.cancelIdleCallback(this._idleCallbackId); + } + + private static readonly onIdle = (): void => { + if (this._renderTarget === undefined) { + return; + } + // Now, update it + this._renderTarget.commandsController.update(); + this._idleCallbackId = -1; + this._renderTarget = undefined; + }; +} + +// window.requestIdleCallback is not supported on Safari +// hence we need to add fallback with setTimeout + +window.requestIdleCallback = + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + window.requestIdleCallback || customRequestIdleCallback; + +function customRequestIdleCallback( + callback: (deadline: { didTimeout: boolean; timeRemaining: () => number }) => void +): number { + const start = Date.now(); + setTimeout(function () { + callback({ + didTimeout: false, + timeRemaining: function () { + return Math.max(0, 50 - (Date.now() - start)); + } + }); + }, 1); + return 1; +} diff --git a/react-components/src/architecture/base/reactUpdaters/DomainObjectPanelUpdater.ts b/react-components/src/architecture/base/reactUpdaters/DomainObjectPanelUpdater.ts index eab63ab5c26..6463ff1c495 100644 --- a/react-components/src/architecture/base/reactUpdaters/DomainObjectPanelUpdater.ts +++ b/react-components/src/architecture/base/reactUpdaters/DomainObjectPanelUpdater.ts @@ -3,6 +3,8 @@ */ import { type DomainObject } from '../domainObjects/DomainObject'; +import { Changes } from '../domainObjectsHelpers/Changes'; +import { type DomainObjectChange } from '../domainObjectsHelpers/DomainObjectChange'; export type DomainObjectInfo = { domainObject: DomainObject }; export type SetDomainObjectInfoDelegate = (domainObjectInfo?: DomainObjectInfo) => void; @@ -26,12 +28,33 @@ export class DomainObjectPanelUpdater { this._setDomainObject = value; } - public static update(domainObject: DomainObject | undefined): void { + public static hide(): void { if (this._setDomainObject === undefined) { return; } - if (domainObject === undefined) { - this._setDomainObject(undefined); + this._setDomainObject(undefined); + } + + public static notify(domainObject: DomainObject, change: DomainObjectChange): void { + if (!this.isActive) { + return; + } + if (domainObject.isSelected) { + if (change.isChanged(Changes.deleted)) { + this.hide(); + } + if (change.isChanged(Changes.selected, Changes.geometry, Changes.naming)) { + this.update(domainObject); + } + } else { + if (change.isChanged(Changes.selected)) { + this.hide(); // Deselected + } + } + } + + private static update(domainObject: DomainObject): void { + if (this._setDomainObject === undefined) { return; } const info = { domainObject }; diff --git a/react-components/src/architecture/base/renderTarget/ToolController.ts b/react-components/src/architecture/base/renderTarget/CommandsController.ts similarity index 98% rename from react-components/src/architecture/base/renderTarget/ToolController.ts rename to react-components/src/architecture/base/renderTarget/CommandsController.ts index b204500f1ad..20420a5cf2b 100644 --- a/react-components/src/architecture/base/renderTarget/ToolController.ts +++ b/react-components/src/architecture/base/renderTarget/CommandsController.ts @@ -1,13 +1,13 @@ /*! * Copyright 2024 Cognite AS - * ToolController: Holds the tools, the active tool and the previous tool + * CommandsController: Holds the tools, the active tool and the previous tool */ import { PointerEvents, PointerEventsTarget } from '@cognite/reveal'; import { type BaseTool } from '../commands/BaseTool'; import { type BaseCommand } from '../commands/BaseCommand'; -export class ToolControllers extends PointerEvents { +export class CommandsController extends PointerEvents { // ================================================== // INSTANCE FIELDS // ================================================== diff --git a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts index 6bac37e06ad..78a9332fd37 100644 --- a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts +++ b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts @@ -18,7 +18,7 @@ import { type WebGLRenderer, type Plane } from 'three'; -import { ToolControllers } from './ToolController'; +import { CommandsController } from './CommandsController'; import { RootDomainObject } from '../domainObjects/RootDomainObject'; import { getOctDir } from '../utilities/extensions/vectorExtensions'; import { getResizeCursor } from '../utilities/geometry/getResizeCursor'; @@ -28,6 +28,7 @@ import { type DomainObject } from '../domainObjects/DomainObject'; import { type AxisGizmoTool } from '@cognite/reveal/tools'; import { type BaseRevealConfig } from './BaseRevealConfig'; import { DefaultRevealConfig } from './DefaultRevealConfig'; +import { CommandsUpdater } from '../reactUpdaters/CommandsUpdater'; const DIRECTIONAL_LIGHT_NAME = 'DirectionalLight'; @@ -37,7 +38,7 @@ export class RevealRenderTarget { // ================================================== private readonly _viewer: Cognite3DViewer; - private readonly _toolController: ToolControllers; + private readonly _commandsController: CommandsController; private readonly _rootDomainObject: RootDomainObject; private _ambientLight: AmbientLight | undefined; private _directionalLight: DirectionalLight | undefined; @@ -57,8 +58,8 @@ export class RevealRenderTarget { if (!isFlexibleCameraManager(cameraManager)) { throw new Error('Can not use RevealRenderTarget without the FlexibleCameraManager'); } - this._toolController = new ToolControllers(this.domElement); - this._toolController.addEventListeners(); + this._commandsController = new CommandsController(this.domElement); + this._commandsController.addEventListeners(); this._rootDomainObject = new RootDomainObject(this); this.initializeLights(); @@ -92,8 +93,8 @@ export class RevealRenderTarget { return this._viewer.domElement; } - public get toolController(): ToolControllers { - return this._toolController; + public get commandsController(): CommandsController { + return this._commandsController; } public get cursor(): string { @@ -137,8 +138,8 @@ export class RevealRenderTarget { const defaultTool = config.createDefaultTool(); defaultTool.attach(this); - this.toolController.add(defaultTool); - this.toolController.setDefaultTool(defaultTool); + this.commandsController.add(defaultTool); + this.commandsController.setDefaultTool(defaultTool); const axisGizmoTool = config.createAxisGizmoTool(); if (axisGizmoTool !== undefined) { @@ -148,15 +149,17 @@ export class RevealRenderTarget { } public dispose(): void { + this._viewer.dispose(); if (this._ambientLight !== undefined) { this._viewer.removeObject3D(this._ambientLight); } if (this._directionalLight !== undefined) { this._viewer.removeObject3D(this._directionalLight); } - this.toolController.removeEventListeners(); - this.toolController.dispose(); + this.commandsController.removeEventListeners(); + this.commandsController.dispose(); this._axisGizmoTool?.dispose(); + CommandsUpdater.dispose(); } public invalidate(): void { diff --git a/react-components/src/architecture/base/utilities/sprites/createSprite.ts b/react-components/src/architecture/base/utilities/sprites/createSprite.ts index 0a08cd909b4..db9afa61aa7 100644 --- a/react-components/src/architecture/base/utilities/sprites/createSprite.ts +++ b/react-components/src/architecture/base/utilities/sprites/createSprite.ts @@ -107,7 +107,7 @@ function createCanvasWithText( ): HTMLCanvasElement | undefined { // https://www.javascripture.com/CanvasRenderingContext2D const borderSize = 2; - const fontSize = 40; + const fontSize = 60; // This gives the resolution of the sprite, not real font side. const font = getNormalFont(fontSize); const canvas = document.createElement('canvas'); diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts index 49a70857e83..155991050d6 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts @@ -6,7 +6,7 @@ import { MeasureBoxRenderStyle } from './MeasureBoxRenderStyle'; import { type RenderStyle } from '../../base/domainObjectsHelpers/RenderStyle'; import { type ThreeView } from '../../base/views/ThreeView'; import { MeasureBoxView } from './MeasureBoxView'; -import { Box3, Matrix4, Vector3 } from 'three'; +import { Box3, Color, Matrix4, Vector3 } from 'three'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { BoxFace } from '../../base/utilities/box/BoxFace'; import { FocusType } from '../../base/domainObjectsHelpers/FocusType'; @@ -19,12 +19,15 @@ import { NumberType, PanelInfo } from '../../base/domainObjectsHelpers/PanelInfo import { radToDeg } from 'three/src/math/MathUtils.js'; import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; import { Range3 } from '../../base/utilities/geometry/Range3'; +import { RootDomainObject } from '../../base/domainObjects/RootDomainObject'; +import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; +import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; export const MIN_BOX_SIZE = 0.01; export class MeasureBoxDomainObject extends MeasureDomainObject { // ================================================== - // INSTANCE FIELDS (This implements the IBox interface) + // INSTANCE FIELDS // ================================================== public readonly size = new Vector3().setScalar(MIN_BOX_SIZE); @@ -44,11 +47,12 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { } // ================================================== - // CONSTRUCTORS + // CONSTRUCTOR // ================================================== public constructor(measureType: MeasureType) { super(measureType); + this.color = new Color(Color.NAMES.magenta); } // ================================================== @@ -62,11 +66,30 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { public override createDragger(props: CreateDraggerProps): BaseDragger | undefined { const pickInfo = props.intersection.userData as BoxPickInfo; if (pickInfo === undefined) { - return undefined; // If the BoxPickInfo isn't specified, no dragger iscreated + return undefined; // If the BoxPickInfo isn't specified, no dragger is created } return new MeasureBoxDragger(props, this); } + protected override notifyCore(change: DomainObjectChange): void { + super.notifyCore(change); + + // Experimental code for crop box (let it stay here) + // if (this.isUseAsCropBox) { + // if (change.isChanged(Changes.deleted)) { + // this.setUseAsCropBox(false); + // } + // if (change.isChanged(Changes.geometry)) { + // this.setUseAsCropBox(true); + // } + // } else if (change.isChanged(Changes.selected) && this.isSelected) { + // const root = this.root as RootDomainObject; + // if (root instanceof RootDomainObject && root.renderTarget.isGlobalCropBoxActive) { + // this.setUseAsCropBox(true); + // } + // } + } + public override getPanelInfo(): PanelInfo | undefined { const info = new PanelInfo(); const { measureType } = this; @@ -224,6 +247,31 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { this.notify(Changes.focus); return true; } + + public get isUseAsCropBox(): boolean { + const root = this.root as RootDomainObject; + if (!(root instanceof RootDomainObject)) { + return false; + } + return root.renderTarget.isGlobalCropBox(this); + } + + public setUseAsCropBox(use: boolean): void { + const root = this.root as RootDomainObject; + if (!(root instanceof RootDomainObject)) { + return; + } + if (!use) { + root.renderTarget.clearGlobalCropBox(); + } else { + const boundingBox = this.getBoundingBox(); + boundingBox.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION); + const matrix = this.getMatrix(); + matrix.premultiply(CDF_TO_VIEWER_TRANSFORMATION); + const planes = BoxFace.createClippingPlanes(matrix); + root.renderTarget.setGlobalCropBox(planes, boundingBox, this); + } + } } // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts index f81638ede4a..8724e5f5873 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts @@ -6,7 +6,6 @@ import { type Ray, Vector3, Plane, Matrix4 } from 'three'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { type BoxFace } from '../../base/utilities/box/BoxFace'; import { FocusType } from '../../base/domainObjectsHelpers/FocusType'; -import { type DomainObject } from '../../base/domainObjects/DomainObject'; import { type BoxPickInfo } from '../../base/utilities/box/BoxPickInfo'; import { forceBetween0AndPi } from '../../base/utilities/extensions/mathExtensions'; import { horizontalAngle } from '../../base/utilities/extensions/vectorExtensions'; @@ -15,7 +14,10 @@ import { MeasureType } from './MeasureType'; import { getClosestPointOnLine } from '../../base/utilities/extensions/rayExtensions'; import { type MeasureBoxDomainObject } from './MeasureBoxDomainObject'; import { BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; -import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; +import { + type VisualDomainObject, + type CreateDraggerProps +} from '../../base/domainObjects/VisualDomainObject'; /** * The `BoxDragger` class represents a utility for dragging and manipulating a box in a 3D space. @@ -83,7 +85,7 @@ export class MeasureBoxDragger extends BaseDragger { // OVERRIDES // ================================================== - public override get domainObject(): DomainObject { + public override get domainObject(): VisualDomainObject { return this._domainObject; } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts index 8ff105bf68f..8fde9669ae9 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts @@ -52,6 +52,7 @@ const RELATIVE_ROTATION_RADIUS = new Range1(0.6, 0.75); const ARROW_AND_RING_COLOR = new Color(1, 1, 1); const TOP_FACE = new BoxFace(2); const CIRCULAR_SEGMENTS = 32; +const RENDER_ORDER = 100; export class MeasureBoxView extends GroupThreeView { // ================================================== @@ -79,15 +80,7 @@ export class MeasureBoxView extends GroupThreeView { public override update(change: DomainObjectChange): void { super.update(change); - if (this.isEmpty) { - return; - } - if ( - change.isChanged(Changes.focus) || - change.isChanged(Changes.selected) || - change.isChanged(Changes.renderStyle) || - change.isChanged(Changes.color) - ) { + if (change.isChanged(Changes.selected, Changes.focus, Changes.renderStyle, Changes.color)) { this.removeChildren(); this.invalidateBoundingBox(); this.invalidateRenderTarget(); @@ -213,6 +206,7 @@ export class MeasureBoxView extends GroupThreeView { updateSolidMaterial(material, domainObject, style); const geometry = new BoxGeometry(1, 1, 1); const result = new Mesh(geometry, material); + result.renderOrder = RENDER_ORDER; result.applyMatrix4(matrix); return result; } @@ -225,6 +219,7 @@ export class MeasureBoxView extends GroupThreeView { updateLineSegmentsMaterial(material, domainObject, style); const geometry = createLineSegmentsBufferGeometryForBox(); const result = new LineSegments(geometry, material); + result.renderOrder = RENDER_ORDER; result.applyMatrix4(matrix); return result; @@ -281,13 +276,14 @@ export class MeasureBoxView extends GroupThreeView { const material = new MeshPhongMaterial(); updateMarkerMaterial(material, domainObject, style, focusType === FocusType.Rotation); material.clippingPlanes = BoxFace.createClippingPlanes(matrix, TOP_FACE.index); - const mesh = new Mesh(geometry, material); + const result = new Mesh(geometry, material); + result.renderOrder = RENDER_ORDER; const center = TOP_FACE.getCenter(newVector3()); center.applyMatrix4(matrix); - mesh.position.copy(center); - mesh.rotateX(-Math.PI / 2); - return mesh; + result.position.copy(center); + result.rotateX(-Math.PI / 2); + return result; } private createEdgeCircle(matrix: Matrix4, material: Material, face: BoxFace): Mesh | undefined { @@ -304,27 +300,28 @@ export class MeasureBoxView extends GroupThreeView { const geometry = new CircleGeometry(radius, CIRCULAR_SEGMENTS); material.transparent = true; material.depthWrite = false; - const mesh = new Mesh(geometry, material); + const result = new Mesh(geometry, material); + result.renderOrder = RENDER_ORDER; const center = face.getCenter(newVector3()); center.applyMatrix4(matrix); - mesh.position.copy(center); + result.position.copy(center); // Must be roteted correctly because of sideness if (face.face === 2) { - mesh.rotateX(-Math.PI / 2); + result.rotateX(-Math.PI / 2); } else if (face.face === 5) { - mesh.rotateX(Math.PI / 2); + result.rotateX(Math.PI / 2); } else if (face.face === 0) { - mesh.rotateY(Math.PI / 2 + domainObject.zRotation); + result.rotateY(Math.PI / 2 + domainObject.zRotation); } else if (face.face === 3) { - mesh.rotateY(-Math.PI / 2 + domainObject.zRotation); + result.rotateY(-Math.PI / 2 + domainObject.zRotation); } else if (face.face === 1) { - mesh.rotateY(Math.PI + domainObject.zRotation); + result.rotateY(Math.PI + domainObject.zRotation); } else if (face.face === 4) { - mesh.rotateY(domainObject.zRotation); + result.rotateY(domainObject.zRotation); } - return mesh; + return result; } // ================================================== @@ -612,6 +609,7 @@ function createSprite(text: string, style: MeasureRenderStyle, height: number): result.material.transparent = true; result.material.opacity = style.textOpacity; result.material.depthTest = style.depthTest; + result.renderOrder = RENDER_ORDER; return result; } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts index 200efd6cd99..ab40a47703f 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts @@ -5,9 +5,6 @@ import { VisualDomainObject } from '../../base/domainObjects/VisualDomainObject'; import { getIconByMeasureType, getNameByMeasureType, type MeasureType } from './MeasureType'; import { type MeasureRenderStyle } from './MeasureRenderStyle'; -import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; -import { DomainObjectPanelUpdater } from '../../base/reactUpdaters/DomainObjectPanelUpdater'; -import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; export abstract class MeasureDomainObject extends VisualDomainObject { @@ -26,7 +23,7 @@ export abstract class MeasureDomainObject extends VisualDomainObject { } // ================================================== - // CONSTRUCTORS + // CONSTRUCTOR // ================================================== public constructor(measureType: MeasureType) { @@ -46,28 +43,12 @@ export abstract class MeasureDomainObject extends VisualDomainObject { return getNameByMeasureType(this.measureType); } + public override get hasPanelInfo(): boolean { + return true; + } + public override getPanelInfoStyle(): PopupStyle { // bottom = 66 because the toolbar is below return new PopupStyle({ bottom: 66, left: 0 }); } - - protected override notifyCore(change: DomainObjectChange): void { - super.notifyCore(change); - - if (!DomainObjectPanelUpdater.isActive) { - return; - } - if (this.isSelected) { - if (change.isChanged(Changes.deleted)) { - DomainObjectPanelUpdater.update(undefined); - } - if (change.isChanged(Changes.selected, Changes.geometry)) { - DomainObjectPanelUpdater.update(this); - } - } else { - if (change.isChanged(Changes.selected)) { - DomainObjectPanelUpdater.update(undefined); // Deselected - } - } - } } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineDomainObject.ts index 4cf8c08c67a..5fc700de2a8 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineDomainObject.ts @@ -5,7 +5,7 @@ import { type RenderStyle } from '../../base/domainObjectsHelpers/RenderStyle'; import { type ThreeView } from '../../base/views/ThreeView'; import { MeasureLineView } from './MeasureLineView'; -import { Vector3 } from 'three'; +import { Color, Vector3 } from 'three'; import { MeasureType } from './MeasureType'; import { MeasureLineRenderStyle } from './MeasureLineRenderStyle'; import { MeasureDomainObject } from './MeasureDomainObject'; @@ -35,11 +35,12 @@ export class MeasureLineDomainObject extends MeasureDomainObject { } // ================================================== - // CONSTRUCTORS + // CONSTRUCTOR // ================================================== public constructor(measureType: MeasureType) { super(measureType); + this.color = new Color(Color.NAMES.red); } // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts index d1a12125ed8..5a6765943a7 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts @@ -11,9 +11,10 @@ export class MeasureLineRenderStyle extends MeasureRenderStyle { // INSTANCE FIELDS // ================================================== - public pipeRadius = 0.015; + public pipeRadius = 0.02; + public selectedPipeRadius = this.pipeRadius * 2; public lineWidth = 1; - public selectedLineWidth = 2; + public selectedLineWidth = this.lineWidth * 2; // ================================================== // OVERRIDES of BaseStyle diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts index c60287d628f..2b536e4ceff 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts @@ -37,6 +37,7 @@ import { FocusType } from '../../base/domainObjectsHelpers/FocusType'; import { MeasureRenderStyle } from './MeasureRenderStyle'; const CYLINDER_DEFAULT_AXIS = new Vector3(0, 1, 0); +const RENDER_ORDER = 100; export class MeasureLineView extends GroupThreeView { // ================================================== @@ -57,15 +58,7 @@ export class MeasureLineView extends GroupThreeView { public override update(change: DomainObjectChange): void { super.update(change); - if (this.isEmpty) { - return; - } - if ( - change.isChanged(Changes.focus) || - change.isChanged(Changes.selected) || - change.isChanged(Changes.renderStyle) || - change.isChanged(Changes.color) - ) { + if (change.isChanged(Changes.selected, Changes.focus, Changes.renderStyle, Changes.color)) { this.removeChildren(); this.invalidateBoundingBox(); this.invalidateRenderTarget(); @@ -98,7 +91,7 @@ export class MeasureLineView extends GroupThreeView { private createPipe(): Mesh | undefined { const { domainObject, style } = this; - const radius = style.pipeRadius; + const radius = domainObject.isSelected ? style.selectedPipeRadius : style.pipeRadius; if (radius <= 0) { return; } @@ -162,7 +155,9 @@ export class MeasureLineView extends GroupThreeView { worldUnits: true, depthTest: style.depthTest }); - return new Wireframe(geometry, material); + const result = new Wireframe(geometry, material); + result.renderOrder = RENDER_ORDER; + return result; } private createLines(): Line | undefined { @@ -179,7 +174,9 @@ export class MeasureLineView extends GroupThreeView { color, depthTest: style.depthTest }); - return new Line(geometry, material); + const result = new Line(geometry, material); + result.renderOrder = RENDER_ORDER; + return result; function createBufferGeometry(vertices: number[]): BufferGeometry { const verticesArray = new Float32Array(vertices); @@ -256,6 +253,7 @@ function createSprite(text: string, style: MeasureRenderStyle, height: number): result.material.transparent = true; result.material.opacity = style.textOpacity; result.material.depthTest = style.depthTest; + result.renderOrder = RENDER_ORDER; return result; } @@ -265,12 +263,11 @@ function updateSolidMaterial( style: MeasureLineRenderStyle ): void { const color = boxDomainObject.getColorByColorType(style.colorType); - const selected = boxDomainObject.isSelected; material.color = color; material.opacity = 1; material.transparent = true; material.emissive = color; - material.emissiveIntensity = selected ? 0.6 : 0.2; + material.emissiveIntensity = boxDomainObject.isSelected ? 0.75 : 0.5; material.side = FrontSide; material.flatShading = false; material.depthWrite = false; diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts index 0bd5541251f..53a499bf8fe 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts @@ -15,7 +15,7 @@ export abstract class MeasureRenderStyle extends RenderStyle { public depthTest = true; public colorType = ColorType.Specified; public textColor = WHITE_COLOR.clone(); - public textBgColor = new Color().setScalar(0.05); // Dark gray - public textOpacity = 0.75; + public textBgColor = new Color('#232323'); + public textOpacity = 0.9; public relativeTextSize = 0.05; // Relative to diagonal of the measurment object for box and average of lenght of line segments for line } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index efdbe2416a1..a316bc01e8d 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -22,6 +22,7 @@ import { ShowMeasurmentsOnTopCommand } from './ShowMeasurmentsOnTopCommand'; import { SetMeasurmentTypeCommand } from './SetMeasurmentTypeCommand'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; import { type RootDomainObject } from '../../base/domainObjects/RootDomainObject'; +import { CommandsUpdater } from '../../base/reactUpdaters/CommandsUpdater'; export class MeasurementTool extends BaseEditTool { // ================================================== @@ -71,9 +72,8 @@ export class MeasurementTool extends BaseEditTool { public override onDeactivate(): void { this.handleEscape(); - super.onDeactivate(); this.setAllMeasurementsVisible(false); - this.deselectAll(); + super.onDeactivate(); } public override clearDragging(): void { @@ -173,7 +173,7 @@ export class MeasurementTool extends BaseEditTool { if (creator.isFinished) { this._creator = undefined; this.measureType = MeasureType.None; - this.renderTarget.toolController.update(); + CommandsUpdater.update(renderTarget); } return; } @@ -209,7 +209,7 @@ export class MeasurementTool extends BaseEditTool { if (creator.addPoint(ray, intersection)) { if (creator.isFinished) { this.measureType = MeasureType.None; - this.renderTarget.toolController.update(); + CommandsUpdater.update(renderTarget); this._creator = undefined; } } @@ -223,6 +223,14 @@ export class MeasurementTool extends BaseEditTool { await super.onPointerDown(event, leftButton); } + // ================================================== + // OVERRIDES of BaseEditTool + // ================================================== + + protected override canBeSelected(domainObject: DomainObject): boolean { + return domainObject instanceof MeasureDomainObject; + } + // ================================================== // INSTANCE METHODS // ================================================== @@ -234,7 +242,7 @@ export class MeasurementTool extends BaseEditTool { if (this._creator.handleEscape()) { // Sucessfully created, set it back to none this.measureType = MeasureType.None; - this.renderTarget.toolController.update(); + CommandsUpdater.update(this.renderTarget); } this._creator = undefined; } diff --git a/react-components/src/architecture/concrete/boxDomainObject/SetCropBoxCommand.ts b/react-components/src/architecture/concrete/boxDomainObject/SetCropBoxCommand.ts new file mode 100644 index 00000000000..64007891a36 --- /dev/null +++ b/react-components/src/architecture/concrete/boxDomainObject/SetCropBoxCommand.ts @@ -0,0 +1,71 @@ +/*! + * Copyright 2024 Cognite AS + * BaseTool: Base class for the tool are used to interact with the render target. + */ + +import { RenderTargetCommand } from '../../base/commands/RenderTargetCommand'; +import { type Tooltip } from '../../base/commands/BaseCommand'; +import { MeasureBoxDomainObject } from './MeasureBoxDomainObject'; +import { MeasureType } from './MeasureType'; + +// Experimental code for crop box + +export class SetCropBoxCommand extends RenderTargetCommand { + // ================================================== + // OVERRIDES + // ================================================== + + public override get tooltip(): Tooltip { + return { key: 'CROP_BOX', fallback: 'Set as crop box' }; + } + + public override get icon(): string { + return 'Crop'; + } + + public override get isEnabled(): boolean { + if (this.renderTarget.isGlobalCropBoxActive) { + return true; + } + return this.getMeasureBoxDomainObject() !== undefined; + } + + public override get isCheckable(): boolean { + return true; + } + + public override get isChecked(): boolean { + return this.renderTarget.isGlobalCropBoxActive; + } + + protected override invokeCore(): boolean { + const { renderTarget } = this; + const domainObject = this.getMeasureBoxDomainObject(); + if (domainObject === undefined || this.renderTarget.isGlobalCropBoxActive) { + renderTarget.clearGlobalCropBox(); + return false; + } + if (domainObject.isUseAsCropBox) { + renderTarget.clearGlobalCropBox(); + return true; + } + domainObject.setUseAsCropBox(true); + renderTarget.fitView(); + return true; + } + + // ================================================== + // INSTANCE METHODS + // ================================================== + + private getMeasureBoxDomainObject(): MeasureBoxDomainObject | undefined { + const domainObject = this.rootDomainObject.getSelectedDescendantByType(MeasureBoxDomainObject); + if (domainObject === undefined) { + return undefined; + } + if (domainObject.measureType !== MeasureType.Volume) { + return undefined; + } + return domainObject; + } +} diff --git a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts index 51b3017b80f..8a1cb259ad5 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts @@ -12,7 +12,7 @@ export class SetMeasurmentTypeCommand extends RenderTargetCommand { private readonly _measureType: MeasureType; // ================================================== - // CONSTRUCTORS + // CONSTRUCTOR // ================================================== public constructor(measureType: MeasureType) { @@ -75,7 +75,7 @@ export class SetMeasurmentTypeCommand extends RenderTargetCommand { // ================================================== private get measurementTool(): MeasurementTool | undefined { - const activeTool = this.renderTarget.toolController.activeTool; + const activeTool = this.renderTarget.commandsController.activeTool; if (!(activeTool instanceof MeasurementTool)) { return undefined; } diff --git a/react-components/src/architecture/concrete/config/StoryBookConfig.ts b/react-components/src/architecture/concrete/config/StoryBookConfig.ts index ba1accbc731..520ece22f65 100644 --- a/react-components/src/architecture/concrete/config/StoryBookConfig.ts +++ b/react-components/src/architecture/concrete/config/StoryBookConfig.ts @@ -28,11 +28,11 @@ export class StoryBookConfig extends BaseRevealConfig { new FitViewCommand(), new SetAxisVisibleCommand(), undefined, - new SetTerrainVisibleCommand(), - new UpdateTerrainCommand(), - undefined, new ExampleTool(), - new MeasurementTool() + new MeasurementTool(), + undefined, + new SetTerrainVisibleCommand(), + new UpdateTerrainCommand() ]; } diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts index f8fc5313ce1..a2fb3b0f25a 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts @@ -13,9 +13,6 @@ import { } from '../../base/domainObjects/VisualDomainObject'; import { Vector3 } from 'three'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; -import { DomainObjectPanelUpdater } from '../../base/reactUpdaters/DomainObjectPanelUpdater'; -import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; -import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { type BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; import { ExampleDragger } from './ExampleDragger'; @@ -38,10 +35,6 @@ export class ExampleDomainObject extends VisualDomainObject { // OVERRIDES of DomainObject // ================================================== - public override get canBeRemoved(): boolean { - return true; - } - public override get icon(): string { return 'Circle'; } @@ -50,6 +43,10 @@ export class ExampleDomainObject extends VisualDomainObject { return 'Example'; } + public override get canBeRemoved(): boolean { + return true; + } + public override createRenderStyle(): RenderStyle | undefined { return new ExampleRenderStyle(); } @@ -58,6 +55,10 @@ export class ExampleDomainObject extends VisualDomainObject { return new ExampleDragger(props, this); } + public override get hasPanelInfo(): boolean { + return true; + } + public override getPanelInfo(): PanelInfo | undefined { const info = new PanelInfo(); info.setHeader('NAME', this.name); @@ -76,25 +77,6 @@ export class ExampleDomainObject extends VisualDomainObject { return new PopupStyle({ bottom: 66, left: 0 }); } - protected override notifyCore(change: DomainObjectChange): void { - super.notifyCore(change); - - if (!DomainObjectPanelUpdater.isActive) { - return; - } - if (this.isSelected) { - if (change.isChanged(Changes.deleted)) { - DomainObjectPanelUpdater.update(undefined); - } - if (change.isChanged(Changes.selected, Changes.geometry, Changes.naming)) { - DomainObjectPanelUpdater.update(this); - } - } else { - if (change.isChanged(Changes.selected)) { - DomainObjectPanelUpdater.update(undefined); // Deselected - } - } - } // ================================================== // OVERRIDES of VisualDomainObject // ================================================== diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts index 5b472f34e96..334617a94c8 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts @@ -4,10 +4,12 @@ import { type Ray, Vector3, Plane } from 'three'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; -import { type DomainObject } from '../../base/domainObjects/DomainObject'; import { type ExampleDomainObject } from './ExampleDomainObject'; import { BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; -import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; +import { + type VisualDomainObject, + type CreateDraggerProps +} from '../../base/domainObjects/VisualDomainObject'; export class ExampleDragger extends BaseDragger { // ================================================== @@ -17,6 +19,7 @@ export class ExampleDragger extends BaseDragger { private readonly _domainObject: ExampleDomainObject; private readonly _center: Vector3; private readonly _plane: Plane; + private readonly _offset: Vector3; // ================================================== // CONTRUCTOR @@ -27,22 +30,33 @@ export class ExampleDragger extends BaseDragger { this._domainObject = domainObject; this._center = this._domainObject.center.clone(); this._plane = new Plane().setFromNormalAndCoplanarPoint(this.ray.direction, this._center); + + // This is the adjustment for hittig the sphere other places than in the center + const planeIntersection = this.ray.intersectPlane(this._plane, new Vector3()); + if (planeIntersection === null) { + throw new Error('Failed to intersect plane'); + } + this._offset = planeIntersection.sub(this._center); } // ================================================== // OVERRIDES // ================================================== - public override get domainObject(): DomainObject { + public override get domainObject(): VisualDomainObject { return this._domainObject; } public override onPointerDrag(_event: PointerEvent, ray: Ray): boolean { - const planeIntersect = ray.intersectPlane(this._plane, new Vector3()); - if (planeIntersect === null) { + const planeIntersection = ray.intersectPlane(this._plane, new Vector3()); + if (planeIntersection === null) { return false; } - this._domainObject.center.copy(planeIntersect); + planeIntersection.sub(this._offset); + if (planeIntersection.equals(this._center)) { + return false; // No change + } + this._domainObject.center.copy(planeIntersection); this.domainObject.notify(Changes.geometry); return true; } diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts index 27711a8a4aa..6e6581944b7 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts @@ -11,7 +11,7 @@ export class ExampleRenderStyle extends RenderStyle { // ================================================== public radius = 1; - public opacity = 0.5; + public opacity = 0.75; // ================================================== // OVERRIDES of BaseStyle diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts index 52c030902a8..b9f847338f4 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts @@ -3,15 +3,16 @@ */ import { ExampleDomainObject } from './ExampleDomainObject'; -import { CDF_TO_VIEWER_TRANSFORMATION, type AnyIntersection } from '@cognite/reveal'; +import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; import { type BaseCommand, type Tooltip } from '../../base/commands/BaseCommand'; -import { isDomainObjectIntersection } from '../../base/domainObjectsHelpers/DomainObjectIntersection'; import { BaseEditTool } from '../../base/commands/BaseEditTool'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { ResetAllExamplesCommand } from './commands/ResetAllExamplesCommand'; import { DeleteAllExamplesCommand } from './commands/DeleteAllExamplesCommand'; import { ShowAllExamplesCommand } from './commands/ShowAllExamplesCommand'; import { clamp } from 'lodash'; +import { type DomainObject } from '../../base/domainObjects/DomainObject'; +import { type HSL } from 'three'; export class ExampleTool extends BaseEditTool { // ================================================== // OVERRIDES of BaseCommand @@ -22,18 +23,13 @@ export class ExampleTool extends BaseEditTool { } public override get tooltip(): Tooltip { - return { key: 'EXAMPLE_EDIT', fallback: 'Create or edit a point' }; + return { key: 'EXAMPLE_EDIT', fallback: 'Create or edit a single point' }; } // ================================================== // OVERRIDES of BaseTool // ================================================== - public override onDeactivate(): void { - super.onDeactivate(); - this.deselectAll(); - } - public override onKey(event: KeyboardEvent, down: boolean): void { if (down && event.key === 'Delete') { const domainObject = this.rootDomainObject.getSelectedDescendantByType(ExampleDomainObject); @@ -47,12 +43,20 @@ export class ExampleTool extends BaseEditTool { public override async onWheel(event: WheelEvent): Promise { const intersection = await this.getIntersection(event); - const domainObject = this.getExampleDomainObject(intersection); + const domainObject = this.getIntersectedDomainObject(intersection) as ExampleDomainObject; if (domainObject === undefined || !domainObject.isSelected) { await super.onWheel(event); return; } - if (event.ctrlKey) { + if (event.shiftKey) { + // Change color + let hsl: HSL = { h: 0, s: 0, l: 0 }; + hsl = domainObject.color.getHSL(hsl); + hsl.h = (hsl.h + Math.sign(event.deltaY) * 0.02) % 1; + domainObject.color.setHSL(hsl.h, hsl.s, hsl.l); + domainObject.notify(Changes.color); + } else if (event.ctrlKey) { + // Change opacity const delta = Math.sign(event.deltaY) * 0.05; domainObject.renderStyle.opacity = clamp(domainObject.renderStyle.opacity + delta, 0.2, 1); domainObject.notify(Changes.renderStyle); @@ -66,9 +70,8 @@ export class ExampleTool extends BaseEditTool { public override async onHover(event: PointerEvent): Promise { const intersection = await this.getIntersection(event); - const domainObject = this.getExampleDomainObject(intersection); - - if (domainObject instanceof ExampleDomainObject) { + // Just set the cursor + if (this.getIntersectedDomainObject(intersection) !== undefined) { this.renderTarget.setMoveCursor(); } else if (intersection !== undefined) { this.renderTarget.setCrosshairCursor(); @@ -78,15 +81,13 @@ export class ExampleTool extends BaseEditTool { } public override async onClick(event: PointerEvent): Promise { - const { renderTarget, rootDomainObject } = this; - const intersection = await this.getIntersection(event); if (intersection === undefined) { await super.onClick(event); return; } { - const domainObject = this.getExampleDomainObject(intersection); + const domainObject = this.getIntersectedDomainObject(intersection); if (domainObject !== undefined) { this.deselectAll(domainObject); domainObject.setSelectedInteractive(true); @@ -101,8 +102,8 @@ export class ExampleTool extends BaseEditTool { domainObject.center.copy(center); this.deselectAll(); - rootDomainObject.addChildInteractive(domainObject); - domainObject.setVisibleInteractive(true, renderTarget); + this.rootDomainObject.addChildInteractive(domainObject); + domainObject.setVisibleInteractive(true, this.renderTarget); domainObject.setSelectedInteractive(true); } @@ -115,19 +116,10 @@ export class ExampleTool extends BaseEditTool { } // ================================================== - // INSTANCE METHODS + // OVERRIDES of BaseEditTool // ================================================== - private getExampleDomainObject( - intersection: AnyIntersection | undefined - ): ExampleDomainObject | undefined { - if (!isDomainObjectIntersection(intersection)) { - return undefined; - } - if (intersection.domainObject instanceof ExampleDomainObject) { - return intersection.domainObject; - } else { - return undefined; - } + protected override canBeSelected(domainObject: DomainObject): boolean { + return domainObject instanceof ExampleDomainObject; } } diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts index eac264506ea..11b6842bca3 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts @@ -35,15 +35,7 @@ export class ExampleView extends GroupThreeView { public override update(change: DomainObjectChange): void { super.update(change); - if (this.isEmpty) { - return; - } - if ( - change.isChanged(Changes.geometry) || - change.isChanged(Changes.selected) || - change.isChanged(Changes.renderStyle) || - change.isChanged(Changes.color) - ) { + if (change.isChanged(Changes.selected, Changes.renderStyle, Changes.color)) { this.removeChildren(); this.invalidateBoundingBox(); this.invalidateRenderTarget(); diff --git a/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts b/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts index 0ff53c31d66..11606735822 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts @@ -14,7 +14,7 @@ export class ResetAllExamplesCommand extends RenderTargetCommand { // ================================================== public override get tooltip(): Tooltip { - return { key: 'EXAMPLES_RESET', fallback: 'Reset' }; + return { key: 'EXAMPLES_RESET', fallback: 'Reset all examples' }; } public override get icon(): string { diff --git a/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts b/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts index fdf4b34616d..ec288b355f3 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts @@ -13,7 +13,7 @@ export class ShowAllExamplesCommand extends RenderTargetCommand { // ================================================== public override get tooltip(): Tooltip { - return { key: 'EXAMPLES_SHOW', fallback: 'Show all examples' }; + return { key: 'EXAMPLES_SHOW', fallback: 'Show or hide all examples' }; } public override get icon(): string { diff --git a/react-components/src/components/Architecture/CommandButton.tsx b/react-components/src/components/Architecture/CommandButton.tsx index 8f81600c371..22ad3fb44a4 100644 --- a/react-components/src/components/Architecture/CommandButton.tsx +++ b/react-components/src/components/Architecture/CommandButton.tsx @@ -10,11 +10,17 @@ import { type BaseCommand } from '../../architecture/base/commands/BaseCommand'; import { type RevealRenderTarget } from '../../architecture/base/renderTarget/RevealRenderTarget'; import { RenderTargetCommand } from '../../architecture/base/commands/RenderTargetCommand'; -export const CreateButton = (command: BaseCommand): ReactElement => { - return ; +export const CreateButton = (command: BaseCommand, isHorizontal = false): ReactElement => { + return ; }; -export const CommandButton = ({ command }: { command: BaseCommand }): ReactElement => { +export const CommandButton = ({ + command, + isHorizontal = false +}: { + command: BaseCommand; + isHorizontal: boolean; +}): ReactElement => { const renderTarget = useRenderTarget(); const { t } = useTranslation(); const newCommand = useMemo(() => getDefaultCommand(command, renderTarget), []); @@ -22,12 +28,14 @@ export const CommandButton = ({ command }: { command: BaseCommand }): ReactEleme const [isChecked, setChecked] = useState(false); const [isEnabled, setEnabled] = useState(true); const [isVisible, setVisible] = useState(true); + const [icon, setIcon] = useState('Copy'); useEffect(() => { function update(command: BaseCommand): void { setChecked(command.isChecked); setEnabled(command.isEnabled); setVisible(command.isVisible); + setIcon(command.icon as IconType); } update(newCommand); newCommand.addEventListener(update); @@ -39,12 +47,13 @@ export const CommandButton = ({ command }: { command: BaseCommand }): ReactEleme if (!isVisible) { return <>; } + const placement = isHorizontal ? 'top' : 'right'; const { key, fallback } = newCommand.tooltip; return ( - +