@babylonjs/inspector 8.42.0-preview → 8.43.0-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{captureService-CbpbBn5F.js → captureService-B8vCTKYD.js} +8 -4
- package/lib/{captureService-CbpbBn5F.js.map → captureService-B8vCTKYD.js.map} +1 -1
- package/lib/{exportService-LaUVRgd_.js → exportService-D1VDR7BX.js} +7 -3
- package/lib/{exportService-LaUVRgd_.js.map → exportService-D1VDR7BX.js.map} +1 -1
- package/lib/{extensionsListService-CwnEf0dV.js → extensionsListService-DS-uWdsz.js} +7 -3
- package/lib/{extensionsListService-CwnEf0dV.js.map → extensionsListService-DS-uWdsz.js.map} +1 -1
- package/lib/{importService-B98QFvNM.js → importService-BPzwIgjH.js} +7 -3
- package/lib/{importService-B98QFvNM.js.map → importService-BPzwIgjH.js.map} +1 -1
- package/lib/{index-BgzFAhky.js → index-2Bq-qBwV.js} +2145 -392
- package/lib/index-2Bq-qBwV.js.map +1 -0
- package/lib/index.d.ts +139 -10
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/{quickCreateToolsService-Bg2plbI-.js → quickCreateToolsService-DEdXDqIl.js} +8 -4
- package/lib/{quickCreateToolsService-Bg2plbI-.js.map → quickCreateToolsService-DEdXDqIl.js.map} +1 -1
- package/package.json +1 -1
- package/lib/index-BgzFAhky.js.map +0 -1
|
@@ -3,13 +3,14 @@ import { createContext, useContext, useMemo, useEffect, useState, useRef, useCal
|
|
|
3
3
|
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
|
|
4
4
|
import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
|
|
5
5
|
import { Observable } from '@babylonjs/core/Misc/observable.js';
|
|
6
|
-
import { makeStyles, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody,
|
|
7
|
-
import { ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, Copy20Regular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, DocumentTextRegular, createFluentIcon, FilterRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular,
|
|
6
|
+
import { makeStyles, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, createLightTheme, createDarkTheme, FluentProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Portal, RendererProvider, createDOMRenderer, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, treeItemLevelToken, MenuItemCheckbox, Switch as Switch$1, PresenceBadge, useId, SpinButton as SpinButton$1, Slider, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Spinner, Badge, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, Label, ToolbarDivider, Field } from '@fluentui/react-components';
|
|
7
|
+
import { ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, Copy20Regular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, DocumentTextRegular, createFluentIcon, FilterRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, EyeRegular, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ChevronDownRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
|
|
8
8
|
import { Collapse as Collapse$1, Fade } from '@fluentui/react-motion-components-preview';
|
|
9
9
|
import '@babylonjs/core/Misc/typeStore.js';
|
|
10
10
|
import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
|
|
11
11
|
import { AsyncLock } from '@babylonjs/core/Misc/asyncLock.js';
|
|
12
12
|
import { Deferred } from '@babylonjs/core/Misc/deferred.js';
|
|
13
|
+
import { Logger } from '@babylonjs/core/Misc/logger.js';
|
|
13
14
|
import { Clamp } from '@babylonjs/core/Maths/math.scalar.functions.js';
|
|
14
15
|
import { VirtualizerScrollView } from '@fluentui-contrib/react-virtualizer';
|
|
15
16
|
import { FontAsset } from '@babylonjs/addons/msdfText/fontAsset.js';
|
|
@@ -32,7 +33,6 @@ import { PerfCollectionStrategy } from '@babylonjs/core/Misc/PerformanceViewer/p
|
|
|
32
33
|
import '@babylonjs/core/Misc/PerformanceViewer/performanceViewerSceneExtension.js';
|
|
33
34
|
import { PressureObserverWrapper } from '@babylonjs/core/Misc/pressureObserverWrapper.js';
|
|
34
35
|
import { AbstractEngine } from '@babylonjs/core/Engines/abstractEngine.js';
|
|
35
|
-
import { Logger } from '@babylonjs/core/Misc/logger.js';
|
|
36
36
|
import { createRoot } from 'react-dom/client';
|
|
37
37
|
import { FrameGraphUtils } from '@babylonjs/core/FrameGraph/frameGraphUtils.js';
|
|
38
38
|
import { CameraGizmo } from '@babylonjs/core/Gizmos/cameraGizmo.js';
|
|
@@ -94,17 +94,21 @@ import { ImageProcessingConfiguration } from '@babylonjs/core/Materials/imagePro
|
|
|
94
94
|
import { Skeleton } from '@babylonjs/core/Bones/skeleton.js';
|
|
95
95
|
import { Sprite } from '@babylonjs/core/Sprites/sprite.js';
|
|
96
96
|
import { SpriteManager } from '@babylonjs/core/Sprites/spriteManager.js';
|
|
97
|
-
import {
|
|
97
|
+
import { GetTextureDataAsync, WhenTextureReadyAsync } from '@babylonjs/core/Misc/textureTools.js';
|
|
98
98
|
import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture.js';
|
|
99
99
|
import { MultiRenderTarget } from '@babylonjs/core/Materials/Textures/multiRenderTarget.js';
|
|
100
100
|
import { RenderTargetTexture } from '@babylonjs/core/Materials/Textures/renderTargetTexture.js';
|
|
101
101
|
import { ThinTexture } from '@babylonjs/core/Materials/Textures/thinTexture.js';
|
|
102
|
+
import { KeyboardEventTypes } from '@babylonjs/core/Events/keyboardEvents.js';
|
|
103
|
+
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
|
|
104
|
+
import { HtmlElementTexture } from '@babylonjs/core/Materials/Textures/htmlElementTexture.js';
|
|
105
|
+
import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial.js';
|
|
106
|
+
import { CreatePlane } from '@babylonjs/core/Meshes/Builders/planeBuilder.js';
|
|
102
107
|
import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer.js';
|
|
103
108
|
import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
|
|
104
109
|
import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
|
|
105
110
|
import '@babylonjs/core/Sprites/spriteSceneComponent.js';
|
|
106
111
|
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture.js';
|
|
107
|
-
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
|
|
108
112
|
import { EngineStore } from '@babylonjs/core/Engines/engineStore.js';
|
|
109
113
|
import { UniqueIdGenerator } from '@babylonjs/core/Misc/uniqueIdGenerator.js';
|
|
110
114
|
import { DebugLayer } from '@babylonjs/core/Debug/debugLayer.js';
|
|
@@ -598,7 +602,10 @@ function CreateGenericForwardRef(render) {
|
|
|
598
602
|
*
|
|
599
603
|
* NOTE: BoundProperty has strict nullable enforcement!
|
|
600
604
|
*
|
|
601
|
-
* If Target[PropertyKey] is Nullable, caller
|
|
605
|
+
* If Target[PropertyKey] is Nullable, caller has three options:
|
|
606
|
+
* 1. `nullable: true` + `defaultValue: NonNullable<T>` - Shows enable/disable checkbox UI
|
|
607
|
+
* 2. `ignoreNullable: true` + `defaultValue: NonNullable<T>` - Shows disabled state when null
|
|
608
|
+
* 3. `defaultValue: null` - Skips nullable handling entirely, passes value through as-is
|
|
602
609
|
*
|
|
603
610
|
* @param props BoundPropertyProps with strict nullable validation
|
|
604
611
|
* @returns JSX element
|
|
@@ -885,7 +892,7 @@ const LinkToEntityPropertyLine = (props) => {
|
|
|
885
892
|
!linkedEntity.reservedDataStore?.hidden && jsx(LinkPropertyLine, { ...rest, value: linkedEntity.name, onLink: () => (selectionService.selectedEntity = linkedEntity) }));
|
|
886
893
|
};
|
|
887
894
|
|
|
888
|
-
const useStyles$
|
|
895
|
+
const useStyles$r = makeStyles({
|
|
889
896
|
accordion: {
|
|
890
897
|
overflowX: "hidden",
|
|
891
898
|
overflowY: "auto",
|
|
@@ -929,13 +936,13 @@ const useStyles$j = makeStyles({
|
|
|
929
936
|
});
|
|
930
937
|
const AccordionSection = (props) => {
|
|
931
938
|
AccordionSection.displayName = "AccordionSection";
|
|
932
|
-
const classes = useStyles$
|
|
939
|
+
const classes = useStyles$r();
|
|
933
940
|
return jsx("div", { className: classes.panelDiv, children: props.children });
|
|
934
941
|
};
|
|
935
942
|
const StringAccordion = Accordion$1;
|
|
936
943
|
const Accordion = forwardRef((props, ref) => {
|
|
937
944
|
Accordion.displayName = "Accordion";
|
|
938
|
-
const classes = useStyles$
|
|
945
|
+
const classes = useStyles$r();
|
|
939
946
|
const { size } = useContext(ToolContext);
|
|
940
947
|
const { children, highlightSections, ...rest } = props;
|
|
941
948
|
const validChildren = useMemo(() => {
|
|
@@ -1067,7 +1074,7 @@ function AsReadonlyArray(array) {
|
|
|
1067
1074
|
return array;
|
|
1068
1075
|
}
|
|
1069
1076
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1070
|
-
const useStyles$
|
|
1077
|
+
const useStyles$q = makeStyles({
|
|
1071
1078
|
rootDiv: {
|
|
1072
1079
|
flex: 1,
|
|
1073
1080
|
overflow: "hidden",
|
|
@@ -1076,7 +1083,7 @@ const useStyles$i = makeStyles({
|
|
|
1076
1083
|
},
|
|
1077
1084
|
});
|
|
1078
1085
|
function ExtensibleAccordion(props) {
|
|
1079
|
-
const classes = useStyles$
|
|
1086
|
+
const classes = useStyles$q();
|
|
1080
1087
|
const { children, sections, sectionContent, context, sectionsRef } = props;
|
|
1081
1088
|
const defaultSections = useMemo(() => {
|
|
1082
1089
|
const defaultSections = [];
|
|
@@ -1181,7 +1188,7 @@ function ExtensibleAccordion(props) {
|
|
|
1181
1188
|
})] }) })) }));
|
|
1182
1189
|
}
|
|
1183
1190
|
|
|
1184
|
-
const useStyles$
|
|
1191
|
+
const useStyles$p = makeStyles({
|
|
1185
1192
|
paneRootDiv: {
|
|
1186
1193
|
display: "flex",
|
|
1187
1194
|
flex: 1,
|
|
@@ -1194,12 +1201,73 @@ const useStyles$h = makeStyles({
|
|
|
1194
1201
|
*/
|
|
1195
1202
|
const SidePaneContainer = forwardRef((props, ref) => {
|
|
1196
1203
|
const { className, ...rest } = props;
|
|
1197
|
-
const classes = useStyles$
|
|
1204
|
+
const classes = useStyles$p();
|
|
1198
1205
|
return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
|
|
1199
1206
|
});
|
|
1200
1207
|
|
|
1208
|
+
const ThemeModeStorageKey = `Babylon/Settings/ThemeMode`;
|
|
1209
|
+
/**
|
|
1210
|
+
* Custom hook to manage the theme mode (system/dark/light).
|
|
1211
|
+
* @returns An object containing the theme mode state and helper functions.
|
|
1212
|
+
*/
|
|
1213
|
+
function useThemeMode() {
|
|
1214
|
+
const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
|
|
1215
|
+
localStorageKey: ThemeModeStorageKey,
|
|
1216
|
+
});
|
|
1217
|
+
// Make sure there is a stored value initially, even before changing the theme.
|
|
1218
|
+
// This way, other usages of this hook will get the correct initial value.
|
|
1219
|
+
if (!localStorage.getItem(ThemeModeStorageKey)) {
|
|
1220
|
+
SetThemeMode(ternaryDarkMode);
|
|
1221
|
+
}
|
|
1222
|
+
return { isDarkMode, themeMode: ternaryDarkMode, setThemeMode: setTernaryDarkMode };
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Sets the theme mode.
|
|
1226
|
+
* @param mode The desired theme mode (system/dark/light).
|
|
1227
|
+
*/
|
|
1228
|
+
function SetThemeMode(mode) {
|
|
1229
|
+
localStorage.setItem(ThemeModeStorageKey, JSON.stringify(mode));
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
1233
|
+
// Generated from https://react.fluentui.dev/?path=/docs/theme-theme-designer--docs
|
|
1234
|
+
// Key color: #3A94FC
|
|
1235
|
+
const babylonRamp = {
|
|
1236
|
+
10: "#020305",
|
|
1237
|
+
20: "#121721",
|
|
1238
|
+
30: "#1A263A",
|
|
1239
|
+
40: "#1F314F",
|
|
1240
|
+
50: "#243E64",
|
|
1241
|
+
60: "#294B7B",
|
|
1242
|
+
70: "#2D5892",
|
|
1243
|
+
80: "#3166AA",
|
|
1244
|
+
90: "#3473C3",
|
|
1245
|
+
100: "#3782DC",
|
|
1246
|
+
110: "#3990F6",
|
|
1247
|
+
120: "#5A9EFD",
|
|
1248
|
+
130: "#7BACFE",
|
|
1249
|
+
140: "#96BAFF",
|
|
1250
|
+
150: "#AFC9FF",
|
|
1251
|
+
160: "#C6D8FF",
|
|
1252
|
+
};
|
|
1253
|
+
const LightTheme = {
|
|
1254
|
+
...createLightTheme(babylonRamp),
|
|
1255
|
+
};
|
|
1256
|
+
const DarkTheme = {
|
|
1257
|
+
...createDarkTheme(babylonRamp),
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
const Theme = (props) => {
|
|
1261
|
+
// NOTE: We do not want to applyStylesToPortals by default. If makes classes flow into portals
|
|
1262
|
+
// (like popovers), and if those styles do things like disable overflow, they can completely
|
|
1263
|
+
// break any UI within the portal. Therefore, default to false.
|
|
1264
|
+
const { invert = false, applyStylesToPortals = false, ...rest } = props;
|
|
1265
|
+
const { isDarkMode } = useThemeMode();
|
|
1266
|
+
return (jsx(FluentProvider, { theme: isDarkMode !== invert ? DarkTheme : LightTheme, applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1201
1269
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1202
|
-
const useStyles$
|
|
1270
|
+
const useStyles$o = makeStyles({
|
|
1203
1271
|
extensionTeachingPopover: {
|
|
1204
1272
|
maxWidth: "320px",
|
|
1205
1273
|
},
|
|
@@ -1210,7 +1278,7 @@ const useStyles$g = makeStyles({
|
|
|
1210
1278
|
* @returns The teaching moment popover.
|
|
1211
1279
|
*/
|
|
1212
1280
|
const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
|
|
1213
|
-
const classes = useStyles$
|
|
1281
|
+
const classes = useStyles$o();
|
|
1214
1282
|
return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
|
|
1215
1283
|
};
|
|
1216
1284
|
|
|
@@ -1466,75 +1534,187 @@ function ConstructorFactory(constructor) {
|
|
|
1466
1534
|
}
|
|
1467
1535
|
|
|
1468
1536
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1469
|
-
const useStyles$
|
|
1537
|
+
const useStyles$n = makeStyles({
|
|
1470
1538
|
placeholderDiv: {
|
|
1471
1539
|
padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
|
|
1472
1540
|
},
|
|
1473
1541
|
});
|
|
1474
1542
|
const PropertiesPane = (props) => {
|
|
1475
|
-
const classes = useStyles$
|
|
1543
|
+
const classes = useStyles$n();
|
|
1476
1544
|
const entity = props.context;
|
|
1477
1545
|
return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
|
|
1478
1546
|
};
|
|
1479
1547
|
|
|
1480
1548
|
const SettingsContextIdentity = Symbol("SettingsContext");
|
|
1481
1549
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
function useThemeMode() {
|
|
1488
|
-
const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
|
|
1489
|
-
localStorageKey: ThemeModeStorageKey,
|
|
1490
|
-
});
|
|
1491
|
-
// Make sure there is a stored value initially, even before changing the theme.
|
|
1492
|
-
// This way, other usages of this hook will get the correct initial value.
|
|
1493
|
-
if (!localStorage.getItem(ThemeModeStorageKey)) {
|
|
1494
|
-
SetThemeMode(ternaryDarkMode);
|
|
1550
|
+
function ToFeaturesString(options) {
|
|
1551
|
+
const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;
|
|
1552
|
+
const features = [];
|
|
1553
|
+
if (defaultWidth !== undefined) {
|
|
1554
|
+
features.push({ key: "width", value: defaultWidth.toString() });
|
|
1495
1555
|
}
|
|
1496
|
-
|
|
1556
|
+
if (defaultHeight !== undefined) {
|
|
1557
|
+
features.push({ key: "height", value: defaultHeight.toString() });
|
|
1558
|
+
}
|
|
1559
|
+
if (defaultLeft !== undefined) {
|
|
1560
|
+
features.push({ key: "left", value: defaultLeft.toString() });
|
|
1561
|
+
}
|
|
1562
|
+
if (defaultTop !== undefined) {
|
|
1563
|
+
features.push({ key: "top", value: defaultTop.toString() });
|
|
1564
|
+
}
|
|
1565
|
+
features.push({ key: "location", value: "no" });
|
|
1566
|
+
return features.map((feature) => `${feature.key}=${feature.value}`).join(",");
|
|
1497
1567
|
}
|
|
1568
|
+
const useStyles$m = makeStyles({
|
|
1569
|
+
container: {
|
|
1570
|
+
display: "flex",
|
|
1571
|
+
flexGrow: 1,
|
|
1572
|
+
flexDirection: "column",
|
|
1573
|
+
overflow: "hidden",
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
1498
1576
|
/**
|
|
1499
|
-
*
|
|
1500
|
-
* @param
|
|
1577
|
+
* Allows displaying a child window that can contain child components.
|
|
1578
|
+
* @param props Props for the child window.
|
|
1579
|
+
* @returns The child window component.
|
|
1501
1580
|
*/
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
//
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
};
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1581
|
+
const ChildWindow = (props) => {
|
|
1582
|
+
const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;
|
|
1583
|
+
const classes = useStyles$m();
|
|
1584
|
+
const [windowState, setWindowState] = useState();
|
|
1585
|
+
const [childWindow, setChildWindow] = useState();
|
|
1586
|
+
const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;
|
|
1587
|
+
// This function is just for creating the child window itself. It is a function because
|
|
1588
|
+
// it must be called synchronously in response to a user interaction (e.g. button click),
|
|
1589
|
+
// otherwise the browser will block it as a scripted popup.
|
|
1590
|
+
const createWindow = useCallback((options = {}) => {
|
|
1591
|
+
if (storageKey) {
|
|
1592
|
+
// If we are persisting window bounds, but the window is already open, just use the existing bounds.
|
|
1593
|
+
// Otherwise, try to load bounds from storage.
|
|
1594
|
+
if (childWindow) {
|
|
1595
|
+
options.defaultLeft = childWindow.screenX;
|
|
1596
|
+
options.defaultTop = childWindow.screenY;
|
|
1597
|
+
options.defaultWidth = childWindow.innerWidth;
|
|
1598
|
+
options.defaultHeight = childWindow.innerHeight;
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
const savedBounds = localStorage.getItem(storageKey);
|
|
1602
|
+
if (savedBounds) {
|
|
1603
|
+
try {
|
|
1604
|
+
const bounds = JSON.parse(savedBounds);
|
|
1605
|
+
options.defaultLeft = bounds.left;
|
|
1606
|
+
options.defaultTop = bounds.top;
|
|
1607
|
+
options.defaultWidth = bounds.width;
|
|
1608
|
+
options.defaultHeight = bounds.height;
|
|
1609
|
+
}
|
|
1610
|
+
catch {
|
|
1611
|
+
Logger.Warn(`Could not parse saved bounds for child window with key ${storageKey}`);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
// Half width by default.
|
|
1617
|
+
if (!options.defaultWidth) {
|
|
1618
|
+
options.defaultWidth = window.innerWidth * (2 / 3);
|
|
1619
|
+
}
|
|
1620
|
+
// Half height by default.
|
|
1621
|
+
if (!options.defaultHeight) {
|
|
1622
|
+
options.defaultHeight = window.innerHeight * (2 / 3);
|
|
1623
|
+
}
|
|
1624
|
+
// Horizontally centered by default.
|
|
1625
|
+
if (!options.defaultLeft) {
|
|
1626
|
+
options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);
|
|
1627
|
+
}
|
|
1628
|
+
// Vertically centered by default.
|
|
1629
|
+
if (!options.defaultTop) {
|
|
1630
|
+
options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);
|
|
1631
|
+
}
|
|
1632
|
+
// Try to create the child window (can be null if popups are blocked).
|
|
1633
|
+
const newChildWindow = window.open("", "", ToFeaturesString(options));
|
|
1634
|
+
if (newChildWindow) {
|
|
1635
|
+
// Set the title if provided.
|
|
1636
|
+
newChildWindow.document.title = options.title ?? id ?? "";
|
|
1637
|
+
// Set the child window state.
|
|
1638
|
+
setChildWindow((current) => {
|
|
1639
|
+
// But first close any existing child window.
|
|
1640
|
+
current?.close();
|
|
1641
|
+
return newChildWindow;
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
}, [childWindow, storageKey]);
|
|
1645
|
+
useImperativeHandle(imperativeRef, () => {
|
|
1646
|
+
return {
|
|
1647
|
+
open: createWindow,
|
|
1648
|
+
close: () => setChildWindow(undefined),
|
|
1649
|
+
};
|
|
1650
|
+
}, [createWindow]);
|
|
1651
|
+
// This side effect runs any time the child window instance changes. It does the rest of the child window
|
|
1652
|
+
// setup work, including creating resources and state needed to properly render the content of the child window.
|
|
1653
|
+
useEffect(() => {
|
|
1654
|
+
const disposeActions = [];
|
|
1655
|
+
if (childWindow) {
|
|
1656
|
+
const body = childWindow.document.body;
|
|
1657
|
+
body.style.width = "100%";
|
|
1658
|
+
body.style.height = "100%";
|
|
1659
|
+
body.style.margin = "0";
|
|
1660
|
+
body.style.padding = "0";
|
|
1661
|
+
body.style.display = "flex";
|
|
1662
|
+
body.style.overflow = "hidden";
|
|
1663
|
+
const applyWindowState = () => {
|
|
1664
|
+
// Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
|
|
1665
|
+
setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
|
|
1666
|
+
onOpenChange?.(true);
|
|
1667
|
+
};
|
|
1668
|
+
// Once the child window document is ready, setup the window state which will trigger another effect that renders into the child window.
|
|
1669
|
+
if (childWindow.document.readyState === "complete") {
|
|
1670
|
+
applyWindowState();
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
const onChildWindowLoad = () => {
|
|
1674
|
+
applyWindowState();
|
|
1675
|
+
};
|
|
1676
|
+
childWindow.addEventListener("load", onChildWindowLoad, { once: true });
|
|
1677
|
+
disposeActions.push(() => childWindow.removeEventListener("load", onChildWindowLoad));
|
|
1678
|
+
}
|
|
1679
|
+
// When the child window is closed for any reason, transition back to a closed state.
|
|
1680
|
+
const onChildWindowUnload = () => {
|
|
1681
|
+
setWindowState(undefined);
|
|
1682
|
+
setChildWindow(undefined);
|
|
1683
|
+
onOpenChange?.(false);
|
|
1684
|
+
};
|
|
1685
|
+
childWindow.addEventListener("unload", onChildWindowUnload, { once: true });
|
|
1686
|
+
disposeActions.push(() => childWindow.removeEventListener("unload", onChildWindowUnload));
|
|
1687
|
+
// If the main window closes, close any open child windows as well (don't leave them orphaned).
|
|
1688
|
+
const onParentWindowUnload = () => {
|
|
1689
|
+
childWindow.close();
|
|
1690
|
+
};
|
|
1691
|
+
window.addEventListener("unload", onParentWindowUnload, { once: true });
|
|
1692
|
+
disposeActions.push(() => window.removeEventListener("unload", onParentWindowUnload));
|
|
1693
|
+
// On dispose, close the child window.
|
|
1694
|
+
disposeActions.push(() => childWindow.close());
|
|
1695
|
+
// On dispose, save the window bounds.
|
|
1696
|
+
disposeActions.push(() => {
|
|
1697
|
+
if (storageKey) {
|
|
1698
|
+
localStorage.setItem(storageKey, JSON.stringify({
|
|
1699
|
+
left: childWindow.screenX,
|
|
1700
|
+
top: childWindow.screenY,
|
|
1701
|
+
width: childWindow.innerWidth,
|
|
1702
|
+
height: childWindow.innerHeight,
|
|
1703
|
+
}));
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
return () => {
|
|
1708
|
+
disposeActions.reverse().forEach((dispose) => dispose());
|
|
1709
|
+
};
|
|
1710
|
+
}, [childWindow]);
|
|
1711
|
+
if (!windowState) {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
const { mountNode, renderer } = windowState;
|
|
1715
|
+
return (
|
|
1716
|
+
// Portal targets the body of the child window.
|
|
1717
|
+
jsx(Portal, { mountNode: mountNode, children: jsx(RendererProvider, { renderer: renderer, targetDocument: mountNode.ownerDocument, children: jsx(FluentProvider, { className: classes.container, applyStylesToPortals: false, targetDocument: mountNode.ownerDocument, children: children }) }) }));
|
|
1538
1718
|
};
|
|
1539
1719
|
|
|
1540
1720
|
// NOTE: This is basically a super simplified version of https://github.com/microsoft/fluentui-contrib/blob/main/packages/react-resize-handle/src/hooks
|
|
@@ -1613,7 +1793,7 @@ function useResizeHandle(params) {
|
|
|
1613
1793
|
const RootComponentServiceIdentity = Symbol("RootComponent");
|
|
1614
1794
|
const ShellServiceIdentity = Symbol("ShellService");
|
|
1615
1795
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1616
|
-
const useStyles$
|
|
1796
|
+
const useStyles$l = makeStyles({
|
|
1617
1797
|
mainView: {
|
|
1618
1798
|
flex: 1,
|
|
1619
1799
|
display: "flex",
|
|
@@ -1801,12 +1981,12 @@ const DockMenu = (props) => {
|
|
|
1801
1981
|
};
|
|
1802
1982
|
const PaneHeader = (props) => {
|
|
1803
1983
|
const { id, title, dockOptions } = props;
|
|
1804
|
-
const classes = useStyles$
|
|
1984
|
+
const classes = useStyles$l();
|
|
1805
1985
|
return (jsx(Theme, { invert: true, children: jsxs("div", { className: classes.paneHeaderDiv, children: [jsx(Subtitle2Stronger, { className: classes.paneHeaderText, children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }) }));
|
|
1806
1986
|
};
|
|
1807
1987
|
// This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
|
|
1808
1988
|
const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
|
|
1809
|
-
const classes = useStyles$
|
|
1989
|
+
const classes = useStyles$l();
|
|
1810
1990
|
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
|
|
1811
1991
|
const teachingMoment = useTeachingMoment(suppressTeachingMoment);
|
|
1812
1992
|
return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: displayName ?? "Extension", description: `The "${displayName ?? id}" extension can be accessed here.` }), jsx("div", { className: classes.barItem, ref: teachingMoment.targetRef, children: jsx(Component, {}) })] }));
|
|
@@ -1814,7 +1994,7 @@ const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Comp
|
|
|
1814
1994
|
// TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
|
|
1815
1995
|
// This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
|
|
1816
1996
|
const Toolbar = ({ location, components }) => {
|
|
1817
|
-
const classes = useStyles$
|
|
1997
|
+
const classes = useStyles$l();
|
|
1818
1998
|
const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
|
|
1819
1999
|
const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
|
|
1820
2000
|
return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : null}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) })] })) }));
|
|
@@ -1824,7 +2004,7 @@ const SidePaneTab = (props) => {
|
|
|
1824
2004
|
const { location, id, isSelected, dockOptions,
|
|
1825
2005
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1826
2006
|
icon: Icon, title, suppressTeachingMoment, } = props;
|
|
1827
|
-
const classes = useStyles$
|
|
2007
|
+
const classes = useStyles$l();
|
|
1828
2008
|
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
|
|
1829
2009
|
const teachingMoment = useTeachingMoment(suppressTeachingMoment);
|
|
1830
2010
|
const tabClass = mergeClasses(classes.tab, isSelected ? undefined : classes.unselectedTab);
|
|
@@ -1837,11 +2017,13 @@ const SidePaneTab = (props) => {
|
|
|
1837
2017
|
// In "compact" mode, the tab list is integrated into the pane itself.
|
|
1838
2018
|
// In "full" mode, the returned tab list is later injected into the toolbar.
|
|
1839
2019
|
function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
|
|
1840
|
-
const classes = useStyles$
|
|
2020
|
+
const classes = useStyles$l();
|
|
1841
2021
|
const [topSelectedTab, setTopSelectedTab] = useState();
|
|
1842
2022
|
const [bottomSelectedTab, setBottomSelectedTab] = useState();
|
|
1843
2023
|
const [collapsed, setCollapsed] = useState(false);
|
|
1844
|
-
const
|
|
2024
|
+
const childWindow = useRef(null);
|
|
2025
|
+
const [isChildWindowOpen, setIsChildWindowOpen] = useState(false);
|
|
2026
|
+
const paneContainerRef = useRef(null);
|
|
1845
2027
|
const onExpandCollapseClick = useCallback(() => {
|
|
1846
2028
|
setCollapsed((collapsed) => !collapsed);
|
|
1847
2029
|
}, []);
|
|
@@ -1898,6 +2080,34 @@ function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSele
|
|
|
1898
2080
|
});
|
|
1899
2081
|
return () => observer.remove();
|
|
1900
2082
|
}, [topPanes, bottomPanes, onSelectSidePane]);
|
|
2083
|
+
const setUndocked = useCallback((undocked) => {
|
|
2084
|
+
if (!undocked) {
|
|
2085
|
+
childWindow.current?.close();
|
|
2086
|
+
}
|
|
2087
|
+
else {
|
|
2088
|
+
const paneContainer = paneContainerRef.current;
|
|
2089
|
+
if (!paneContainer) {
|
|
2090
|
+
// It shouldn't be possible to get here and have this ref be null, but just in case,
|
|
2091
|
+
// bail out of the undock operation.
|
|
2092
|
+
childWindow.current?.close();
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
// This is the extra buffer needed on top of minWidth to account for window chrome to avoid a horizontal scrollbar.
|
|
2096
|
+
const widthBuffer = 4;
|
|
2097
|
+
// This offsets the window's top position to account for window chrome/title bar.
|
|
2098
|
+
const topOffset = 100;
|
|
2099
|
+
// Create the child window with approximately the same location and size as the side pane.
|
|
2100
|
+
const bounds = paneContainer.getBoundingClientRect();
|
|
2101
|
+
childWindow.current?.open({
|
|
2102
|
+
defaultWidth: Math.max(bounds.width, minWidth + widthBuffer),
|
|
2103
|
+
defaultHeight: bounds.height - topOffset,
|
|
2104
|
+
defaultTop: bounds.top + window.screenY + topOffset,
|
|
2105
|
+
defaultLeft: bounds.left + window.screenX,
|
|
2106
|
+
title: location === "left" ? "Left" : "Right",
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}, [childWindow, location]);
|
|
1901
2111
|
const expandCollapseButton = useMemo(() => {
|
|
1902
2112
|
const expandCollapseIcon = location === "left" ? collapsed ? jsx(PanelLeftExpandRegular, {}) : jsx(PanelLeftContractRegular, {}) : collapsed ? jsx(PanelRightExpandRegular, {}) : jsx(PanelRightContractRegular, {});
|
|
1903
2113
|
return (jsxs(Menu, { positioning: "below-end", children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: collapsed ? "Show Side Pane" : "Hide Side Pane", relationship: "label", children: jsx(SplitButton, { className: classes.paneCollapseButton, menuButton: triggerProps, primaryActionButton: { onClick: onExpandCollapseClick }, size: "small", appearance: "transparent", icon: expandCollapseIcon }) })) }), jsx(MenuPopover, { className: classes.collapseMenuPopover, children: jsx(MenuList, { children: jsx(MenuItem, { icon: jsx(PictureInPictureEnterRegular, {}), onClick: () => setUndocked(true), children: "Undock" }) }) })] }));
|
|
@@ -1910,8 +2120,8 @@ function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSele
|
|
|
1910
2120
|
}, children: paneComponents.map((entry) => {
|
|
1911
2121
|
const isSelected = selectedTab?.key === entry.key;
|
|
1912
2122
|
return (jsx(SidePaneTab, { location: location, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment, isSelected: isSelected && !collapsed, dockOptions: dockOptions }, entry.key));
|
|
1913
|
-
}) }) })), toolbarMode === "full" && (jsxs(Fragment, { children: [paneComponents.length > 1 && (jsxs(Fragment, { children: [jsx(Divider, { vertical: true, inset: true, style: { minHeight: 0 } }), " "] })), jsx(Collapse, { visible: !
|
|
1914
|
-
}, [location, collapsed,
|
|
2123
|
+
}) }) })), toolbarMode === "full" && (jsxs(Fragment, { children: [paneComponents.length > 1 && (jsxs(Fragment, { children: [jsx(Divider, { vertical: true, inset: true, style: { minHeight: 0 } }), " "] })), jsx(Collapse, { visible: !isChildWindowOpen, orientation: "horizontal", children: expandCollapseButton })] }))] })) }));
|
|
2124
|
+
}, [location, collapsed, isChildWindowOpen, expandCollapseButton]);
|
|
1915
2125
|
// This memos the TabList to make it easy for the JSX to be inserted at the top of the pane (in "compact" mode) or returned to the caller to be used in the toolbar (in "full" mode).
|
|
1916
2126
|
const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab, validTopDockOptions), [createPaneTabList, topPanes, toolbarMode, topSelectedTab]);
|
|
1917
2127
|
const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab, validBottomDockOptions), [createPaneTabList, bottomPanes, bottomSelectedTab]);
|
|
@@ -1947,82 +2157,15 @@ function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSele
|
|
|
1947
2157
|
setPaneHeightAdjust(Number.parseInt(storedPaneHeightAdjust));
|
|
1948
2158
|
}
|
|
1949
2159
|
}, []);
|
|
1950
|
-
const paneContainerRef = useRef(null);
|
|
1951
|
-
const [windowState, setWindowState] = useState();
|
|
1952
|
-
useEffect(() => {
|
|
1953
|
-
const disposeActions = [];
|
|
1954
|
-
if (undocked) {
|
|
1955
|
-
const paneContainer = paneContainerRef.current;
|
|
1956
|
-
if (!paneContainer) {
|
|
1957
|
-
// It shouldn't be possible to get here and have this ref be null, but just in case,
|
|
1958
|
-
// bail out of the undock operation.
|
|
1959
|
-
setUndocked(false);
|
|
1960
|
-
}
|
|
1961
|
-
else {
|
|
1962
|
-
// This is the extra buffer needed on top of minWidth to account for window chrome to avoid a horizontal scrollbar.
|
|
1963
|
-
const widthBuffer = 4;
|
|
1964
|
-
// This offsets the window's top position to account for window chrome/title bar.
|
|
1965
|
-
const topOffset = 100;
|
|
1966
|
-
// Create the child window with approximately the same location and size as the side pane.
|
|
1967
|
-
const bounds = paneContainer.getBoundingClientRect();
|
|
1968
|
-
const top = bounds.top + window.screenY + topOffset;
|
|
1969
|
-
const left = bounds.left + window.screenX;
|
|
1970
|
-
const width = Math.max(bounds.width, minWidth + widthBuffer);
|
|
1971
|
-
const height = bounds.height - topOffset;
|
|
1972
|
-
const childWindow = window.open("", "", `width=${width},height=${height},left=${left},top=${top},location=no`);
|
|
1973
|
-
if (childWindow) {
|
|
1974
|
-
const body = childWindow.document.body;
|
|
1975
|
-
body.style.width = "100%";
|
|
1976
|
-
body.style.height = "100%";
|
|
1977
|
-
body.style.margin = "0";
|
|
1978
|
-
body.style.padding = "0";
|
|
1979
|
-
body.style.display = "flex";
|
|
1980
|
-
body.style.overflowY = "hidden";
|
|
1981
|
-
body.style.overflowX = "auto";
|
|
1982
|
-
childWindow.document.title = location === "left" ? "Left" : "Right";
|
|
1983
|
-
const applyWindowState = () => {
|
|
1984
|
-
// Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
|
|
1985
|
-
setWindowState({ window: childWindow, mountNode: body, renderer: createDOMRenderer(childWindow.document) });
|
|
1986
|
-
};
|
|
1987
|
-
// Once the child window document is ready, setup the window state which will trigger another effect that renders into the child window.
|
|
1988
|
-
if (childWindow.document.readyState === "complete") {
|
|
1989
|
-
applyWindowState();
|
|
1990
|
-
}
|
|
1991
|
-
else {
|
|
1992
|
-
const onChildWindowLoad = () => {
|
|
1993
|
-
applyWindowState();
|
|
1994
|
-
};
|
|
1995
|
-
childWindow.addEventListener("load", onChildWindowLoad, { once: true });
|
|
1996
|
-
disposeActions.push(() => childWindow.removeEventListener("load", onChildWindowLoad));
|
|
1997
|
-
}
|
|
1998
|
-
// When the child window is closed for any reason, transition back to a docked state.
|
|
1999
|
-
childWindow.addEventListener("unload", () => {
|
|
2000
|
-
setWindowState(undefined);
|
|
2001
|
-
setUndocked(false);
|
|
2002
|
-
}, { once: true });
|
|
2003
|
-
// If the main window closes, close any undocked child windows as well (don't leave them orphaned).
|
|
2004
|
-
const onParentWindowUnload = () => childWindow.close();
|
|
2005
|
-
window.addEventListener("unload", onParentWindowUnload);
|
|
2006
|
-
disposeActions.push(() => window.removeEventListener("unload", onParentWindowUnload));
|
|
2007
|
-
}
|
|
2008
|
-
else {
|
|
2009
|
-
// If creating a child window failed (e.g. popup blocked), then just revert to docked mode.
|
|
2010
|
-
setUndocked(false);
|
|
2011
|
-
}
|
|
2012
|
-
disposeActions.push(() => childWindow?.close());
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
return () => disposeActions.reverse().forEach((dispose) => dispose());
|
|
2016
|
-
}, [undocked]);
|
|
2017
2160
|
// This effect closes the window if all panes have been removed.
|
|
2018
2161
|
useEffect(() => {
|
|
2019
|
-
if (
|
|
2020
|
-
|
|
2162
|
+
if (isChildWindowOpen && topPanes.length === 0 && bottomPanes.length === 0) {
|
|
2163
|
+
childWindow.current?.close();
|
|
2021
2164
|
}
|
|
2022
|
-
}, [
|
|
2165
|
+
}, [childWindow, isChildWindowOpen, topPanes, bottomPanes]);
|
|
2023
2166
|
// This memoizes the pane itself, which may or may not include the tab list, depending on the toolbar mode.
|
|
2024
2167
|
const corePane = useMemo(() => {
|
|
2025
|
-
return (jsxs(Fragment, { children: [toolbarMode === "compact" && (topPanes.length > 1 || topBarItems.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [!
|
|
2168
|
+
return (jsxs(Fragment, { children: [toolbarMode === "compact" && (topPanes.length > 1 || topBarItems.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [!isChildWindowOpen && location === "left" && expandCollapseButton, topPaneTabList, jsx(Toolbar, { location: "top", components: topBarItems }), !isChildWindowOpen && location === "right" && expandCollapseButton] }) })), topPanes.length > 0 && (jsx("div", { className: classes.paneContent, children: topSelectedTab && (jsxs(Fragment, { children: [jsx(PaneHeader, { id: topSelectedTab.key, title: topSelectedTab.title, dockOptions: validTopDockOptions }), topPanes.map((pane) => (jsx("div", { className: mergeClasses(classes.paneContent, pane.key !== topSelectedTab.key ? classes.unselectedPane : undefined), children: jsx(pane.content, {}) }, pane.key)))] })) })), topPanes.length > 0 && bottomPanes.length > 0 && jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.paneDivider }), bottomPanes.length > 1 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), bottomPanes.length > 0 && (jsx("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px, calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: bottomSelectedTab && (jsxs(Fragment, { children: [jsx(PaneHeader, { id: bottomSelectedTab.key, title: bottomSelectedTab.title, dockOptions: validBottomDockOptions }), bottomPanes.map((pane) => (jsx("div", { className: mergeClasses(classes.paneContent, pane.key !== bottomSelectedTab.key ? classes.unselectedPane : undefined), children: jsx(pane.content, {}) }, pane.key)))] })) })), toolbarMode === "compact" && bottomBarItems.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }));
|
|
2026
2169
|
}, [
|
|
2027
2170
|
topPanes,
|
|
2028
2171
|
topSelectedTab,
|
|
@@ -2034,25 +2177,15 @@ function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSele
|
|
|
2034
2177
|
bottomBarItems,
|
|
2035
2178
|
topPaneTabList,
|
|
2036
2179
|
bottomPaneTabList,
|
|
2037
|
-
|
|
2180
|
+
isChildWindowOpen,
|
|
2038
2181
|
]);
|
|
2039
2182
|
// This deals with docked vs undocked state, where undocked is rendered into a separate window via a portal.
|
|
2040
2183
|
const pane = useMemo(() => {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
}
|
|
2047
|
-
else {
|
|
2048
|
-
// Otherwise we are undocked, so render into the portal that targets the body of the child window.
|
|
2049
|
-
const { mountNode, renderer } = windowState;
|
|
2050
|
-
return (
|
|
2051
|
-
// Portal targets the body of the child window.
|
|
2052
|
-
jsx(Portal, { mountNode: mountNode, children: jsx(RendererProvider, { renderer: renderer, targetDocument: mountNode.ownerDocument, children: jsx(Theme, { className: classes.paneContent, style: { minWidth }, targetDocument: mountNode.ownerDocument, children: corePane }) }) }));
|
|
2053
|
-
}
|
|
2054
|
-
}, [collapsed, corePane, windowState]);
|
|
2055
|
-
return [topPaneTabList, pane, collapsed, setCollapsed, undocked, setUndocked];
|
|
2184
|
+
return (jsxs(Fragment, { children: [!isChildWindowOpen && (jsx("div", { ref: paneContainerRef, className: mergeClasses(classes.paneContainer, layoutMode === "inline"
|
|
2185
|
+
? undefined
|
|
2186
|
+
: mergeClasses(classes.paneContainerOverlay, location === "left" ? classes.paneContainerOverlayLeft : classes.paneContainerOverlayRight)), children: (topPanes.length > 0 || bottomPanes.length > 0) && (jsxs("div", { className: `${classes.pane} ${location === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsx("div", { ref: paneHorizontalResizeElementRef, className: classes.paneContainer, style: { width: `clamp(${minWidth}px, calc(${defaultWidth}px + var(${paneWidthAdjustCSSVar}, 0px)), 1000px)` }, children: corePane }) }), jsx("div", { ref: paneHorizontalResizeHandleRef, className: `${classes.resizer} ${location === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` } })] })) })), jsx(ChildWindow, { imperativeRef: childWindow, onOpenChange: (isOpen) => setIsChildWindowOpen(isOpen), children: corePane })] }));
|
|
2187
|
+
}, [collapsed, corePane]);
|
|
2188
|
+
return [topPaneTabList, pane, collapsed, setCollapsed, isChildWindowOpen, setUndocked];
|
|
2056
2189
|
}
|
|
2057
2190
|
function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneRemapper = undefined, layoutMode = "inline", } = {}) {
|
|
2058
2191
|
return {
|
|
@@ -2077,7 +2210,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
2077
2210
|
undock: () => onDockChanged.notifyObservers({ location: "right", dock: false }),
|
|
2078
2211
|
};
|
|
2079
2212
|
const rootComponent = () => {
|
|
2080
|
-
const classes = useStyles$
|
|
2213
|
+
const classes = useStyles$l();
|
|
2081
2214
|
const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSidePaneDockOverrides();
|
|
2082
2215
|
// This function returns a promise that resolves after the dock change takes effect so that
|
|
2083
2216
|
// we can then select the re-docked pane.
|
|
@@ -2513,7 +2646,7 @@ function useCommandContextMenuState(commands) {
|
|
|
2513
2646
|
return [checkedContextMenuItems, onContextMenuCheckedValueChange, contextMenuItems];
|
|
2514
2647
|
}
|
|
2515
2648
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
2516
|
-
const useStyles$
|
|
2649
|
+
const useStyles$k = makeStyles({
|
|
2517
2650
|
rootDiv: {
|
|
2518
2651
|
flex: 1,
|
|
2519
2652
|
overflow: "hidden",
|
|
@@ -2581,14 +2714,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
|
|
|
2581
2714
|
}
|
|
2582
2715
|
const SceneTreeItem = (props) => {
|
|
2583
2716
|
const { isSelected, select } = props;
|
|
2584
|
-
const classes = useStyles$
|
|
2717
|
+
const classes = useStyles$k();
|
|
2585
2718
|
const [compactMode] = useCompactMode();
|
|
2586
2719
|
const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
|
|
2587
2720
|
return (jsx(FlatTreeItem, { value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
|
|
2588
2721
|
};
|
|
2589
2722
|
const SectionTreeItem = (props) => {
|
|
2590
2723
|
const { section, isFiltering, commandProviders, expandAll, collapseAll } = props;
|
|
2591
|
-
const classes = useStyles$
|
|
2724
|
+
const classes = useStyles$k();
|
|
2592
2725
|
const [compactMode] = useCompactMode();
|
|
2593
2726
|
// Get the commands that apply to this section.
|
|
2594
2727
|
const commands = useResource(useCallback(() => {
|
|
@@ -2605,7 +2738,7 @@ const SectionTreeItem = (props) => {
|
|
|
2605
2738
|
};
|
|
2606
2739
|
const EntityTreeItem = (props) => {
|
|
2607
2740
|
const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll } = props;
|
|
2608
|
-
const classes = useStyles$
|
|
2741
|
+
const classes = useStyles$k();
|
|
2609
2742
|
const [compactMode] = useCompactMode();
|
|
2610
2743
|
const hasChildren = !!entityItem.children?.length;
|
|
2611
2744
|
const displayInfo = useResource(useCallback(() => {
|
|
@@ -2698,7 +2831,7 @@ const EntityTreeItem = (props) => {
|
|
|
2698
2831
|
}, children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }, entityItem.entity.uniqueId) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
|
|
2699
2832
|
};
|
|
2700
2833
|
const SceneExplorer = (props) => {
|
|
2701
|
-
const classes = useStyles$
|
|
2834
|
+
const classes = useStyles$k();
|
|
2702
2835
|
const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity } = props;
|
|
2703
2836
|
const [openItems, setOpenItems] = useState(new Set());
|
|
2704
2837
|
const [sceneVersion, setSceneVersion] = useState(0);
|
|
@@ -3493,14 +3626,14 @@ const TextPropertyLine = (props) => {
|
|
|
3493
3626
|
return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value }) }));
|
|
3494
3627
|
};
|
|
3495
3628
|
|
|
3496
|
-
const useStyles$
|
|
3629
|
+
const useStyles$j = makeStyles({
|
|
3497
3630
|
pinnedStatsPane: {
|
|
3498
3631
|
flex: "0 1 auto",
|
|
3499
3632
|
paddingBottom: tokens.spacingHorizontalM,
|
|
3500
3633
|
},
|
|
3501
3634
|
});
|
|
3502
3635
|
const StatsPane = (props) => {
|
|
3503
|
-
const classes = useStyles$
|
|
3636
|
+
const classes = useStyles$j();
|
|
3504
3637
|
const scene = props.context;
|
|
3505
3638
|
const engine = scene.getEngine();
|
|
3506
3639
|
const fps = useObservableState(() => Math.round(engine.getFps()), engine.onBeginFrameObservable);
|
|
@@ -3678,7 +3811,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3678
3811
|
keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
|
|
3679
3812
|
...BabylonWebResources,
|
|
3680
3813
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3681
|
-
getExtensionModuleAsync: async () => await import('./exportService-
|
|
3814
|
+
getExtensionModuleAsync: async () => await import('./exportService-D1VDR7BX.js'),
|
|
3682
3815
|
},
|
|
3683
3816
|
{
|
|
3684
3817
|
name: "Capture Tools",
|
|
@@ -3686,7 +3819,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3686
3819
|
keywords: ["capture", "screenshot", "gif", "video", "tools"],
|
|
3687
3820
|
...BabylonWebResources,
|
|
3688
3821
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3689
|
-
getExtensionModuleAsync: async () => await import('./captureService-
|
|
3822
|
+
getExtensionModuleAsync: async () => await import('./captureService-B8vCTKYD.js'),
|
|
3690
3823
|
},
|
|
3691
3824
|
{
|
|
3692
3825
|
name: "Import Tools",
|
|
@@ -3694,7 +3827,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3694
3827
|
keywords: ["import", "tools"],
|
|
3695
3828
|
...BabylonWebResources,
|
|
3696
3829
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3697
|
-
getExtensionModuleAsync: async () => await import('./importService-
|
|
3830
|
+
getExtensionModuleAsync: async () => await import('./importService-BPzwIgjH.js'),
|
|
3698
3831
|
},
|
|
3699
3832
|
{
|
|
3700
3833
|
name: "Quick Creation Tools (Preview)",
|
|
@@ -3702,7 +3835,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3702
3835
|
keywords: ["creation", "tools"],
|
|
3703
3836
|
...BabylonWebResources,
|
|
3704
3837
|
author: { name: "Babylon.js", forumUserName: "" },
|
|
3705
|
-
getExtensionModuleAsync: async () => await import('./quickCreateToolsService-
|
|
3838
|
+
getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DEdXDqIl.js'),
|
|
3706
3839
|
},
|
|
3707
3840
|
]);
|
|
3708
3841
|
|
|
@@ -3920,7 +4053,7 @@ const Dropdown = (props) => {
|
|
|
3920
4053
|
const NumberDropdown = Dropdown;
|
|
3921
4054
|
const StringDropdown = Dropdown;
|
|
3922
4055
|
|
|
3923
|
-
const useStyles$
|
|
4056
|
+
const useStyles$i = makeStyles({
|
|
3924
4057
|
surface: {
|
|
3925
4058
|
maxWidth: "400px",
|
|
3926
4059
|
},
|
|
@@ -3932,16 +4065,16 @@ const useStyles$b = makeStyles({
|
|
|
3932
4065
|
minWidth: "300px",
|
|
3933
4066
|
},
|
|
3934
4067
|
});
|
|
3935
|
-
const Popover = (props) => {
|
|
4068
|
+
const Popover = forwardRef((props, ref) => {
|
|
3936
4069
|
const { children } = props;
|
|
3937
4070
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
3938
|
-
const classes = useStyles$
|
|
4071
|
+
const classes = useStyles$i();
|
|
3939
4072
|
return (jsxs(Popover$1, { open: popoverOpen, onOpenChange: (_, data) => setPopoverOpen(data.open), positioning: {
|
|
3940
4073
|
align: "start",
|
|
3941
4074
|
overflowBoundary: document.body,
|
|
3942
4075
|
autoSize: true,
|
|
3943
|
-
}, trapFocus: true, children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: props.trigger ?? jsx(Button, { icon: props.icon, onClick: () => setPopoverOpen(true) }) }), jsx(PopoverSurface, { className: classes.surface, children: jsx("div", { className: classes.content, children: children }) })] }));
|
|
3944
|
-
};
|
|
4076
|
+
}, trapFocus: true, children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: props.trigger ?? jsx(Button, { ref: ref, icon: props.icon, onClick: () => setPopoverOpen(true) }) }), jsx(PopoverSurface, { className: classes.surface, children: jsx("div", { className: classes.content, children: children }) })] }));
|
|
4077
|
+
});
|
|
3945
4078
|
|
|
3946
4079
|
const useColorPickerStyles = makeStyles({
|
|
3947
4080
|
container: {
|
|
@@ -3990,28 +4123,29 @@ const useColorPickerStyles = makeStyles({
|
|
|
3990
4123
|
gap: tokens.spacingVerticalSNudge, // 6px
|
|
3991
4124
|
},
|
|
3992
4125
|
});
|
|
3993
|
-
const ColorPickerPopup = (props) => {
|
|
4126
|
+
const ColorPickerPopup = forwardRef((props, ref) => {
|
|
3994
4127
|
ColorPickerPopup.displayName = "ColorPickerPopup";
|
|
4128
|
+
const { value, onChange, isLinearMode, ...rest } = props;
|
|
3995
4129
|
const classes = useColorPickerStyles();
|
|
3996
|
-
const [color, setColor] = useState(
|
|
3997
|
-
const [isLinear, setIsLinear] = useState(
|
|
4130
|
+
const [color, setColor] = useState(value);
|
|
4131
|
+
const [isLinear, setIsLinear] = useState(isLinearMode ?? false);
|
|
3998
4132
|
const [isFloat, setFloat] = useState(false);
|
|
3999
4133
|
const { size } = useContext(ToolContext);
|
|
4000
4134
|
useEffect(() => {
|
|
4001
|
-
setColor(
|
|
4002
|
-
}, [
|
|
4135
|
+
setColor(value); // Ensures the trigger color updates when props.value changes
|
|
4136
|
+
}, [value]);
|
|
4003
4137
|
const handleColorPickerChange = (_, data) => {
|
|
4004
4138
|
let color = Color3.FromHSV(data.color.h, data.color.s, data.color.v);
|
|
4005
|
-
if (
|
|
4139
|
+
if (value instanceof Color4) {
|
|
4006
4140
|
color = Color4.FromColor3(color, data.color.a ?? 1);
|
|
4007
4141
|
}
|
|
4008
4142
|
handleChange(color);
|
|
4009
4143
|
};
|
|
4010
4144
|
const handleChange = (newColor) => {
|
|
4011
4145
|
setColor(newColor);
|
|
4012
|
-
|
|
4146
|
+
onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
|
|
4013
4147
|
};
|
|
4014
|
-
return (jsx(Popover, { trigger: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }), children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
4148
|
+
return (jsx(Popover, { trigger: jsx(ColorSwatch, { ref: ref, ...rest, borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }), children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
4015
4149
|
label: "Color Space",
|
|
4016
4150
|
info: jsx(Body1, { children: "Today this is not mutable as the color space is determined by the entity. Soon we will allow swapping" }),
|
|
4017
4151
|
}, options: [
|
|
@@ -4024,7 +4158,7 @@ const ColorPickerPopup = (props) => {
|
|
|
4024
4158
|
{ label: "Int", value: 0 },
|
|
4025
4159
|
{ label: "Float", value: 1 },
|
|
4026
4160
|
], disabled: true, value: isFloat ? 1 : 0, onChange: (val) => setFloat(val === 1) })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputRgbField, { title: "Red", value: color, rgbKey: "r", onChange: handleChange }), jsx(InputRgbField, { title: "Green", value: color, rgbKey: "g", onChange: handleChange }), jsx(InputRgbField, { title: "Blue", value: color, rgbKey: "b", onChange: handleChange }), jsx(InputAlphaField, { color: color, onChange: handleChange })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputHsvField, { title: "Hue", value: color, hsvKey: "h", max: 360, onChange: handleChange }), jsx(InputHsvField, { title: "Saturation", value: color, hsvKey: "s", max: 100, scale: 100, onChange: handleChange }), jsx(InputHsvField, { title: "Value", value: color, hsvKey: "v", max: 100, scale: 100, onChange: handleChange })] }), jsx("div", { className: classes.inputRow, children: jsx(InputHexField, { title: "Hexadecimal", linearHex: isLinear, isLinearMode: isLinear, value: color, onChange: handleChange }) })] }) }));
|
|
4027
|
-
};
|
|
4161
|
+
});
|
|
4028
4162
|
/**
|
|
4029
4163
|
* Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
|
|
4030
4164
|
* When the hex color is changed by user, component calculates the new Color3/4 value and calls onChange
|
|
@@ -4143,7 +4277,7 @@ const ColorPropertyLine = forwardRef((props, ref) => {
|
|
|
4143
4277
|
const Color3PropertyLine = ColorPropertyLine;
|
|
4144
4278
|
const Color4PropertyLine = ColorPropertyLine;
|
|
4145
4279
|
|
|
4146
|
-
const useStyles$
|
|
4280
|
+
const useStyles$h = makeStyles({
|
|
4147
4281
|
dropdown: {
|
|
4148
4282
|
...UniformWidthStyling,
|
|
4149
4283
|
},
|
|
@@ -4155,7 +4289,7 @@ const useStyles$a = makeStyles({
|
|
|
4155
4289
|
*/
|
|
4156
4290
|
const DropdownPropertyLine = forwardRef((props, ref) => {
|
|
4157
4291
|
DropdownPropertyLine.displayName = "DropdownPropertyLine";
|
|
4158
|
-
const classes = useStyles$
|
|
4292
|
+
const classes = useStyles$h();
|
|
4159
4293
|
return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
|
|
4160
4294
|
});
|
|
4161
4295
|
/**
|
|
@@ -4729,7 +4863,7 @@ class ServiceContainer {
|
|
|
4729
4863
|
}
|
|
4730
4864
|
}
|
|
4731
4865
|
|
|
4732
|
-
const useStyles$
|
|
4866
|
+
const useStyles$g = makeStyles({
|
|
4733
4867
|
themeButton: {
|
|
4734
4868
|
margin: 0,
|
|
4735
4869
|
},
|
|
@@ -4748,7 +4882,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
4748
4882
|
suppressTeachingMoment: true,
|
|
4749
4883
|
order: -300,
|
|
4750
4884
|
component: () => {
|
|
4751
|
-
const classes = useStyles$
|
|
4885
|
+
const classes = useStyles$g();
|
|
4752
4886
|
const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
|
|
4753
4887
|
const onSelectedThemeChange = useCallback((e, data) => {
|
|
4754
4888
|
setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
|
|
@@ -4766,7 +4900,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
4766
4900
|
};
|
|
4767
4901
|
|
|
4768
4902
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
4769
|
-
const useStyles$
|
|
4903
|
+
const useStyles$f = makeStyles({
|
|
4770
4904
|
app: {
|
|
4771
4905
|
colorScheme: "light dark",
|
|
4772
4906
|
flexGrow: 1,
|
|
@@ -4802,7 +4936,7 @@ function MakeModularTool(options) {
|
|
|
4802
4936
|
SetThemeMode(themeMode);
|
|
4803
4937
|
}
|
|
4804
4938
|
const modularToolRootComponent = () => {
|
|
4805
|
-
const classes = useStyles$
|
|
4939
|
+
const classes = useStyles$f();
|
|
4806
4940
|
const [extensionManagerContext, setExtensionManagerContext] = useState();
|
|
4807
4941
|
const [requiredExtensions, setRequiredExtensions] = useState();
|
|
4808
4942
|
const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
|
|
@@ -4828,7 +4962,7 @@ function MakeModularTool(options) {
|
|
|
4828
4962
|
});
|
|
4829
4963
|
// Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
|
|
4830
4964
|
if (extensionFeeds.length > 0) {
|
|
4831
|
-
const { ExtensionListServiceDefinition } = await import('./extensionsListService-
|
|
4965
|
+
const { ExtensionListServiceDefinition } = await import('./extensionsListService-DS-uWdsz.js');
|
|
4832
4966
|
await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
|
|
4833
4967
|
}
|
|
4834
4968
|
// Register the theme selector service (for selecting the theme) if theming is configured.
|
|
@@ -5010,7 +5144,7 @@ const MeshIcon = createFluentIcon("Mesh", "16", '<path d="M14.03,3.54l-5.11-2.07
|
|
|
5010
5144
|
const TranslateIcon = createFluentIcon("Translate", "24", '<path d="M20.16,12.98l-2.75-2.75c-.29-.29-.77-.29-1.06,0-.29.29-.29.77,0,1.06l1.47,1.47h-6.69v-6.69l1.47,1.47c.29.29.77.29,1.06,0,.29-.29.29-.77,0-1.06l-2.75-2.75c-.14-.14-.33-.22-.53-.22s-.39.08-.53.22l-2.75,2.75c-.29.29-.29.77,0,1.06.29.29.77.29,1.06,0l1.47-1.47v7.13l-3.52,3.52v-2.08c0-.41-.34-.75-.75-.75s-.75.34-.75.75v3.89c0,.2.08.39.22.53.14.14.33.22.53.22h3.89c.41,0,.75-.34.75-.75s-.34-.75-.75-.75h-2.08s3.52-3.52,3.52-3.52h7.13l-1.47,1.47c-.29.29-.29.77,0,1.06s.77.29,1.06,0l2.75-2.75c.14-.14.22-.33.22-.53s-.08-.39-.22-.53Z" />');
|
|
5011
5145
|
const MaterialIcon = createFluentIcon("Material", "16", '<path d="M14.74,6.3c-.09-.36-.38-.64-.75-.72-.04-.09-.08-.18-.12-.27.1-.15.16-.32.16-.51,0-.18-.05-.34-.13-.48-1.23-1.97-3.41-3.28-5.9-3.28C4.16,1.04,1.04,4.16,1.04,7.99c0,.39.23.72.57.88.02.12.03.25.06.37-.18.18-.3.42-.3.7,0,.11.02.21.06.31.94,2.74,3.53,4.71,6.58,4.71,3.84,0,6.96-3.12,6.96-6.96,0-.59-.08-1.16-.22-1.7ZM2.07,8.58c-.02-.19-.03-.39-.03-.58,0-3.29,2.67-5.96,5.96-5.96,2.23,0,4.17,1.23,5.2,3.05.05.18-.07.45-.3.75-.57-.73-1.45-1.21-2.45-1.21-1.72,0-3.12,1.4-3.12,3.11,0,.33.07.65.16.95-3.05.82-5.17.52-5.42-.11ZM12.56,7.75c0,1.17-.95,2.11-2.11,2.11s-2.12-.95-2.12-2.11.95-2.11,2.12-2.11,2.11.95,2.11,2.11ZM8,13.96c-2.6,0-4.81-1.68-5.62-4.01.5.16,1.11.24,1.79.24,1.15,0,2.49-.22,3.79-.59.57.76,1.47,1.26,2.49,1.26,1.72,0,3.11-1.4,3.11-3.11,0-.34-.07-.65-.17-.96.13-.13.24-.26.34-.39.14.51.22,1.04.22,1.6,0,3.29-2.67,5.96-5.96,5.96Z"/>');
|
|
5012
5146
|
|
|
5013
|
-
const useStyles$
|
|
5147
|
+
const useStyles$e = makeStyles({
|
|
5014
5148
|
coordinatesModeButton: {
|
|
5015
5149
|
margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
|
|
5016
5150
|
},
|
|
@@ -5020,7 +5154,7 @@ const useStyles$7 = makeStyles({
|
|
|
5020
5154
|
});
|
|
5021
5155
|
const GizmoToolbar = (props) => {
|
|
5022
5156
|
const { scene, entity, gizmoService } = props;
|
|
5023
|
-
const classes = useStyles$
|
|
5157
|
+
const classes = useStyles$e();
|
|
5024
5158
|
const gizmoManager = useResource(useCallback(() => {
|
|
5025
5159
|
const utilityLayerRef = gizmoService.getUtilityLayer(scene);
|
|
5026
5160
|
const keepDepthUtilityLayerRef = gizmoService.getUtilityLayer(scene, "keepDepth");
|
|
@@ -5135,7 +5269,7 @@ const GizmoToolbarServiceDefinition = {
|
|
|
5135
5269
|
},
|
|
5136
5270
|
};
|
|
5137
5271
|
|
|
5138
|
-
const useStyles$
|
|
5272
|
+
const useStyles$d = makeStyles({
|
|
5139
5273
|
badge: {
|
|
5140
5274
|
margin: tokens.spacingHorizontalXXS,
|
|
5141
5275
|
fontFamily: "monospace",
|
|
@@ -5151,7 +5285,7 @@ const MiniStatsServiceDefinition = {
|
|
|
5151
5285
|
horizontalLocation: "right",
|
|
5152
5286
|
suppressTeachingMoment: true,
|
|
5153
5287
|
component: () => {
|
|
5154
|
-
const classes = useStyles$
|
|
5288
|
+
const classes = useStyles$d();
|
|
5155
5289
|
const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
|
|
5156
5290
|
const engine = scene?.getEngine();
|
|
5157
5291
|
const fps = useObservableState(useCallback(() => (engine ? Math.round(engine.getFps()) : null), [engine]), engine?.onBeginFrameObservable);
|
|
@@ -6121,6 +6255,95 @@ const PBRBaseMaterialSheenProperties = (props) => {
|
|
|
6121
6255
|
} }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Roughness", target: material.sheen, propertyKey: "_useRoughness" }), jsx(Collapse, { visible: useRoughness, children: jsx(BoundProperty, { nullable: true, component: SyncedSliderPropertyLine, label: "Roughness", target: material.sheen, propertyKey: "roughness", defaultValue: 0, min: 0, max: 1, step: 0.01 }) }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Roughness from Main Texture", target: material.sheen, propertyKey: "useRoughnessFromMainTexture" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Albedo Scaling", target: material.sheen, propertyKey: "albedoScaling" })] })] }));
|
|
6122
6256
|
};
|
|
6123
6257
|
|
|
6258
|
+
const useStyles$c = makeStyles({
|
|
6259
|
+
root: {
|
|
6260
|
+
display: "grid",
|
|
6261
|
+
gridTemplateRows: "repeat(1fr)",
|
|
6262
|
+
justifyItems: "start",
|
|
6263
|
+
gap: "2px",
|
|
6264
|
+
maxWidth: "400px",
|
|
6265
|
+
},
|
|
6266
|
+
comboBox: {
|
|
6267
|
+
width: CustomTokens.inputWidth,
|
|
6268
|
+
minWidth: CustomTokens.inputWidth,
|
|
6269
|
+
boxSizing: "border-box",
|
|
6270
|
+
},
|
|
6271
|
+
input: {
|
|
6272
|
+
minWidth: 0,
|
|
6273
|
+
},
|
|
6274
|
+
});
|
|
6275
|
+
/**
|
|
6276
|
+
* Wrapper around a Fluent ComboBox that allows for filtering options.
|
|
6277
|
+
* @param props
|
|
6278
|
+
* @returns
|
|
6279
|
+
*/
|
|
6280
|
+
const ComboBox = (props) => {
|
|
6281
|
+
ComboBox.displayName = "ComboBox";
|
|
6282
|
+
const comboId = useId();
|
|
6283
|
+
const styles = useStyles$c();
|
|
6284
|
+
const { size } = useContext(ToolContext);
|
|
6285
|
+
// Find the label for the current value
|
|
6286
|
+
const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
|
|
6287
|
+
const [query, setQuery] = useState(getLabel(props.value ?? ""));
|
|
6288
|
+
useEffect(() => {
|
|
6289
|
+
setQuery(getLabel(props.value ?? ""));
|
|
6290
|
+
}, [props.value, props.options]);
|
|
6291
|
+
// Convert to Fluent's { children, value } format
|
|
6292
|
+
const normalizedOptions = props.options.map((opt) => ({ children: opt.label, value: opt.value }));
|
|
6293
|
+
const children = useComboboxFilter(query, normalizedOptions, {
|
|
6294
|
+
noOptionsMessage: "No items match your search.",
|
|
6295
|
+
optionToReactKey: (option) => option.value,
|
|
6296
|
+
optionToText: (option) => option.children,
|
|
6297
|
+
renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
|
|
6298
|
+
});
|
|
6299
|
+
const onOptionSelect = (_e, data) => {
|
|
6300
|
+
setQuery(data.optionText ?? "");
|
|
6301
|
+
data.optionValue && props.onChange(data.optionValue);
|
|
6302
|
+
};
|
|
6303
|
+
return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { size: size, root: { className: styles.comboBox }, input: { className: styles.input }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
|
|
6304
|
+
};
|
|
6305
|
+
|
|
6306
|
+
/**
|
|
6307
|
+
* A generic primitive component with a ComboBox for selecting from a list of entities.
|
|
6308
|
+
* Supports entities with duplicate names by using uniqueId for identity.
|
|
6309
|
+
* @param props ChooseEntityProps
|
|
6310
|
+
* @returns EntitySelector component
|
|
6311
|
+
*/
|
|
6312
|
+
function EntitySelector(props) {
|
|
6313
|
+
const { value, onChange, getEntities, getName, filter } = props;
|
|
6314
|
+
// Build options with uniqueId as key
|
|
6315
|
+
const options = useMemo(() => {
|
|
6316
|
+
return getEntities()
|
|
6317
|
+
.filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
|
|
6318
|
+
.map((entity) => ({
|
|
6319
|
+
label: getName(entity),
|
|
6320
|
+
value: entity.uniqueId.toString(),
|
|
6321
|
+
}))
|
|
6322
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
6323
|
+
}, [getEntities, getName, filter]);
|
|
6324
|
+
const handleEntitySelect = (key) => {
|
|
6325
|
+
const entity = getEntities().find((e) => e.uniqueId.toString() === key);
|
|
6326
|
+
onChange(entity ?? null);
|
|
6327
|
+
};
|
|
6328
|
+
// Get current entity key for display
|
|
6329
|
+
const currentKey = value ? value.uniqueId.toString() : "";
|
|
6330
|
+
return jsx(ComboBox, { label: "", options: options, value: currentKey, onChange: handleEntitySelect });
|
|
6331
|
+
}
|
|
6332
|
+
EntitySelector.displayName = "EntitySelector";
|
|
6333
|
+
|
|
6334
|
+
/**
|
|
6335
|
+
* A primitive component with a ComboBox for selecting from existing scene materials.
|
|
6336
|
+
* @param props MaterialSelectorProps
|
|
6337
|
+
* @returns MaterialSelector component
|
|
6338
|
+
*/
|
|
6339
|
+
const MaterialSelector = (props) => {
|
|
6340
|
+
MaterialSelector.displayName = "MaterialSelector";
|
|
6341
|
+
const { scene, ...rest } = props;
|
|
6342
|
+
const getMaterials = useCallback(() => scene.materials, [scene.materials]);
|
|
6343
|
+
const getName = useCallback((material) => material.name, []);
|
|
6344
|
+
return jsx(EntitySelector, { ...rest, getEntities: getMaterials, getName: getName });
|
|
6345
|
+
};
|
|
6346
|
+
|
|
6124
6347
|
/**
|
|
6125
6348
|
* A button that uploads a file and either:
|
|
6126
6349
|
* - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
|
|
@@ -6179,50 +6402,7 @@ const TextureUpload = (props) => {
|
|
|
6179
6402
|
return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
|
|
6180
6403
|
};
|
|
6181
6404
|
|
|
6182
|
-
const useStyles$
|
|
6183
|
-
root: {
|
|
6184
|
-
// Stack the label above the field with a gap
|
|
6185
|
-
display: "grid",
|
|
6186
|
-
gridTemplateRows: "repeat(1fr)",
|
|
6187
|
-
justifyItems: "start",
|
|
6188
|
-
gap: "2px",
|
|
6189
|
-
maxWidth: "400px",
|
|
6190
|
-
},
|
|
6191
|
-
comboBox: {
|
|
6192
|
-
width: CustomTokens.inputWidth,
|
|
6193
|
-
minWidth: CustomTokens.inputWidth,
|
|
6194
|
-
boxSizing: "border-box",
|
|
6195
|
-
},
|
|
6196
|
-
input: {
|
|
6197
|
-
minWidth: 0, // Override Fluent's default minWidth on the input
|
|
6198
|
-
},
|
|
6199
|
-
});
|
|
6200
|
-
/**
|
|
6201
|
-
* Wrapper around a Fluent ComboBox that allows for filtering options
|
|
6202
|
-
* @param props
|
|
6203
|
-
* @returns
|
|
6204
|
-
*/
|
|
6205
|
-
const ComboBox = (props) => {
|
|
6206
|
-
ComboBox.displayName = "ComboBox";
|
|
6207
|
-
const comboId = useId();
|
|
6208
|
-
const styles = useStyles$5();
|
|
6209
|
-
const { size } = useContext(ToolContext);
|
|
6210
|
-
const [query, setQuery] = useState(props.value ?? "");
|
|
6211
|
-
// Sync query with props.value when it changes externally
|
|
6212
|
-
useEffect(() => {
|
|
6213
|
-
setQuery(props.value ?? "");
|
|
6214
|
-
}, [props.value]);
|
|
6215
|
-
const children = useComboboxFilter(query, props.options, {
|
|
6216
|
-
noOptionsMessage: "No items match your search.",
|
|
6217
|
-
});
|
|
6218
|
-
const onOptionSelect = (_e, data) => {
|
|
6219
|
-
setQuery(data.optionText ?? "");
|
|
6220
|
-
data.optionText && props.onChange(data.optionText);
|
|
6221
|
-
};
|
|
6222
|
-
return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { size: size, root: { className: styles.comboBox }, input: { className: styles.input }, onOptionSelect: onOptionSelect, onBlur: () => props.onChange(query), "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
|
|
6223
|
-
};
|
|
6224
|
-
|
|
6225
|
-
const useStyles$4 = makeStyles({
|
|
6405
|
+
const useStyles$b = makeStyles({
|
|
6226
6406
|
container: {
|
|
6227
6407
|
display: "flex",
|
|
6228
6408
|
flexDirection: "row",
|
|
@@ -6233,55 +6413,21 @@ const useStyles$4 = makeStyles({
|
|
|
6233
6413
|
/**
|
|
6234
6414
|
* A primitive component with a ComboBox for selecting from existing scene textures
|
|
6235
6415
|
* and a button for uploading new texture files.
|
|
6236
|
-
* @param props
|
|
6237
|
-
* @returns
|
|
6416
|
+
* @param props TextureSelectorProps
|
|
6417
|
+
* @returns TextureSelector component
|
|
6238
6418
|
*/
|
|
6239
|
-
const
|
|
6240
|
-
|
|
6419
|
+
const TextureSelector = (props) => {
|
|
6420
|
+
TextureSelector.displayName = "TextureSelector";
|
|
6241
6421
|
const { scene, cubeOnly, value, onChange } = props;
|
|
6242
|
-
const classes = useStyles$
|
|
6243
|
-
|
|
6244
|
-
const
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
.map((t) => t.displayName || t.name)
|
|
6248
|
-
.sort((a, b) => a.localeCompare(b));
|
|
6249
|
-
}, [scene.textures, cubeOnly]);
|
|
6250
|
-
const handleTextureSelect = (textureName) => {
|
|
6251
|
-
const texture = scene.textures.find((t) => (t.displayName || t.name) === textureName);
|
|
6252
|
-
onChange(texture ?? null);
|
|
6253
|
-
};
|
|
6254
|
-
// Get current texture name for initial display
|
|
6255
|
-
const currentTextureName = value ? value.displayName || value.name : "";
|
|
6256
|
-
return (jsxs("div", { className: classes.container, children: [jsx(ComboBox, { label: "", options: textureOptions, value: currentTextureName, onChange: handleTextureSelect }), jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
|
|
6257
|
-
};
|
|
6258
|
-
|
|
6259
|
-
/**
|
|
6260
|
-
* A property line with a ComboBox for selecting from existing scene textures
|
|
6261
|
-
* and a button for uploading new texture files.
|
|
6262
|
-
* @param props - ChooseTextureProps & PropertyLineProps
|
|
6263
|
-
* @returns property-line wrapped ChooseTexture component
|
|
6264
|
-
*/
|
|
6265
|
-
const ChooseTexturePropertyLine = (props) => {
|
|
6266
|
-
ChooseTexturePropertyLine.displayName = "ChooseTexturePropertyLine";
|
|
6267
|
-
return (jsx(PropertyLine, { ...props, children: jsx(ChooseTexture, { ...props }) }));
|
|
6422
|
+
const classes = useStyles$b();
|
|
6423
|
+
const getTextures = useCallback(() => scene.textures, [scene.textures]);
|
|
6424
|
+
const getName = useCallback((texture) => texture.displayName || texture.name, []);
|
|
6425
|
+
const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
|
|
6426
|
+
return (jsxs("div", { className: classes.container, children: [jsx(EntitySelector, { value: value, onChange: onChange, getEntities: getTextures, getName: getName, filter: filter }), jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
|
|
6268
6427
|
};
|
|
6269
6428
|
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
* @param props - The required properties
|
|
6273
|
-
* @returns ChooseTexturePropertyLine component
|
|
6274
|
-
*/
|
|
6275
|
-
function BoundTextureProperty(props) {
|
|
6276
|
-
const { label, target, propertyKey, scene, cubeOnly } = props;
|
|
6277
|
-
const value = useProperty(target, propertyKey);
|
|
6278
|
-
const notifyPropertyChanged = usePropertyChangedNotifier();
|
|
6279
|
-
return (jsx(ChooseTexturePropertyLine, { label: label, value: value, onChange: (texture) => {
|
|
6280
|
-
const oldValue = target[propertyKey];
|
|
6281
|
-
target[propertyKey] = texture;
|
|
6282
|
-
notifyPropertyChanged(target, propertyKey, oldValue, texture);
|
|
6283
|
-
}, scene: scene, cubeOnly: cubeOnly }));
|
|
6284
|
-
}
|
|
6429
|
+
const MaterialSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(MaterialSelector, { ...props }) });
|
|
6430
|
+
const TextureSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(TextureSelector, { ...props }) });
|
|
6285
6431
|
|
|
6286
6432
|
/**
|
|
6287
6433
|
* Displays the lighting and color properties of a PBR material.
|
|
@@ -6300,7 +6446,7 @@ const PBRMaterialLightingAndColorProperties = (props) => {
|
|
|
6300
6446
|
const PBRMaterialTextureProperties = (props) => {
|
|
6301
6447
|
const { material } = props;
|
|
6302
6448
|
const scene = material.getScene();
|
|
6303
|
-
return (jsxs(Fragment, { children: [jsx(
|
|
6449
|
+
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Albedo", target: material, propertyKey: "albedoTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Metallic Roughness", target: material, propertyKey: "metallicTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, cubeOnly: true, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflectivity", target: material, propertyKey: "reflectivityTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Micro-surface", target: material, propertyKey: "microSurfaceTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene, defaultValue: null })] }));
|
|
6304
6450
|
};
|
|
6305
6451
|
|
|
6306
6452
|
// TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
|
|
@@ -6800,7 +6946,7 @@ function SaveMetadata(entity, metadata) {
|
|
|
6800
6946
|
entity.metadata = metadata;
|
|
6801
6947
|
}
|
|
6802
6948
|
}
|
|
6803
|
-
const useStyles$
|
|
6949
|
+
const useStyles$a = makeStyles({
|
|
6804
6950
|
mainDiv: {
|
|
6805
6951
|
display: "flex",
|
|
6806
6952
|
flexDirection: "column",
|
|
@@ -6820,7 +6966,7 @@ const useStyles$3 = makeStyles({
|
|
|
6820
6966
|
*/
|
|
6821
6967
|
const MetadataProperties = (props) => {
|
|
6822
6968
|
const { entity } = props;
|
|
6823
|
-
const classes = useStyles$
|
|
6969
|
+
const classes = useStyles$a();
|
|
6824
6970
|
const { size } = useContext(ToolContext);
|
|
6825
6971
|
const metadata = useProperty(entity, "metadata");
|
|
6826
6972
|
const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata) ?? "", [metadata]);
|
|
@@ -6875,7 +7021,7 @@ const AbstractMeshGeneralProperties = (props) => {
|
|
|
6875
7021
|
const isAnInstance = useProperty(mesh, "isAnInstance");
|
|
6876
7022
|
// TODO: Handle case where array is mutated
|
|
6877
7023
|
const subMeshes = useProperty(mesh, "subMeshes");
|
|
6878
|
-
return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Vertices", value: mesh.getTotalVertices() }), jsx(StringifiedPropertyLine, { label: "Faces", value: mesh.getTotalIndices() / 3 }), jsx(StringifiedPropertyLine, { label: "Sub-Meshes", value: subMeshes.length }), jsx(LinkToEntityPropertyLine, { label: "Skeleton", description: "The skeleton associated with the mesh.", entity: skeleton, selectionService: selectionService }), jsx(LinkToEntityPropertyLine, { label: "Material", description: "The material used by the mesh.", entity: material, selectionService: selectionService }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Pickable", target: mesh, propertyKey: "isPickable" }), isAnInstance && mesh instanceof InstancedMesh && (jsx(LinkToEntityPropertyLine, { label: "Source", description: "The source mesh from which this instance was created.", entity: mesh.sourceMesh, selectionService: selectionService }))] }));
|
|
7024
|
+
return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Vertices", value: mesh.getTotalVertices() }), jsx(StringifiedPropertyLine, { label: "Faces", value: mesh.getTotalIndices() / 3 }), jsx(StringifiedPropertyLine, { label: "Sub-Meshes", value: subMeshes.length }), jsx(LinkToEntityPropertyLine, { label: "Skeleton", description: "The skeleton associated with the mesh.", entity: skeleton, selectionService: selectionService }), jsx(LinkToEntityPropertyLine, { label: "Material", description: "The material used by the mesh.", entity: material, selectionService: selectionService }), !mesh.isAnInstance && (jsx(BoundProperty, { defaultValue: null, component: MaterialSelectorPropertyLine, label: "Active Material", target: mesh, propertyKey: "material", scene: mesh.getScene() })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Pickable", target: mesh, propertyKey: "isPickable" }), isAnInstance && mesh instanceof InstancedMesh && (jsx(LinkToEntityPropertyLine, { label: "Source", description: "The source mesh from which this instance was created.", entity: mesh.sourceMesh, selectionService: selectionService }))] }));
|
|
6879
7025
|
};
|
|
6880
7026
|
const AbstractMeshDisplayProperties = (props) => {
|
|
6881
7027
|
const { mesh } = props;
|
|
@@ -8052,90 +8198,6 @@ const SpriteManagerCellProperties = (props) => {
|
|
|
8052
8198
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Width", target: spriteManager, propertyKey: "cellWidth", min: 1, step: 1 }, "CellWidth"), jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Height", target: spriteManager, propertyKey: "cellHeight", min: 1, step: 1 }, "CellHeight")] }));
|
|
8053
8199
|
};
|
|
8054
8200
|
|
|
8055
|
-
const useStyles$2 = makeStyles({
|
|
8056
|
-
root: {
|
|
8057
|
-
display: "flex",
|
|
8058
|
-
flexDirection: "column",
|
|
8059
|
-
},
|
|
8060
|
-
controls: {
|
|
8061
|
-
display: "flex",
|
|
8062
|
-
gap: tokens.spacingHorizontalXS,
|
|
8063
|
-
},
|
|
8064
|
-
controlButton: {
|
|
8065
|
-
minWidth: "auto",
|
|
8066
|
-
flex: "1 1 0", // Equal flex grow/shrink with 0 basis
|
|
8067
|
-
paddingVertical: tokens.spacingVerticalXS,
|
|
8068
|
-
paddingHorizontal: tokens.spacingHorizontalS,
|
|
8069
|
-
overflow: "hidden",
|
|
8070
|
-
textOverflow: "ellipsis",
|
|
8071
|
-
},
|
|
8072
|
-
preview: {
|
|
8073
|
-
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
8074
|
-
display: "block",
|
|
8075
|
-
objectFit: "contain",
|
|
8076
|
-
},
|
|
8077
|
-
previewContainer: {
|
|
8078
|
-
display: "flex",
|
|
8079
|
-
justifyContent: "center",
|
|
8080
|
-
marginTop: tokens.spacingVerticalXS,
|
|
8081
|
-
marginBottom: tokens.spacingVerticalS,
|
|
8082
|
-
width: "100%",
|
|
8083
|
-
},
|
|
8084
|
-
});
|
|
8085
|
-
// This method of holding TextureChannels was brought over from inspectorv1 and can likely be refactored/simplified
|
|
8086
|
-
const TextureChannelStates = {
|
|
8087
|
-
R: { R: true, G: false, B: false, A: false },
|
|
8088
|
-
G: { R: false, G: true, B: false, A: false },
|
|
8089
|
-
B: { R: false, G: false, B: true, A: false },
|
|
8090
|
-
A: { R: false, G: false, B: false, A: true },
|
|
8091
|
-
ALL: { R: true, G: true, B: true, A: true },
|
|
8092
|
-
};
|
|
8093
|
-
const TexturePreview = (props) => {
|
|
8094
|
-
const { texture, disableToolbar = false, maxWidth = "100%", maxHeight = "384px", offsetX = 0, offsetY = 0, width, height } = props;
|
|
8095
|
-
const classes = useStyles$2();
|
|
8096
|
-
const canvasRef = useRef(null);
|
|
8097
|
-
const [channels, setChannels] = useState(TextureChannelStates.ALL);
|
|
8098
|
-
const [face, setFace] = useState(0);
|
|
8099
|
-
const [canvasStyle, setCanvasStyle] = useState();
|
|
8100
|
-
const internalTexture = useProperty(texture, "_texture");
|
|
8101
|
-
const { size } = useContext(ToolContext);
|
|
8102
|
-
const updatePreviewAsync = useCallback(async () => {
|
|
8103
|
-
const canvas = canvasRef.current;
|
|
8104
|
-
if (!canvas) {
|
|
8105
|
-
return;
|
|
8106
|
-
}
|
|
8107
|
-
try {
|
|
8108
|
-
await WhenTextureReadyAsync(texture); // Ensure texture is loaded before grabbing size
|
|
8109
|
-
const { width: textureWidth, height: textureHeight } = texture.getSize();
|
|
8110
|
-
// Set canvas dimensions to the sub-region size
|
|
8111
|
-
canvas.width = width ?? textureWidth;
|
|
8112
|
-
canvas.height = height ?? textureHeight;
|
|
8113
|
-
// Calculate the width that corresponds to maxHeight while maintaining aspect ratio
|
|
8114
|
-
const aspectRatio = canvas.width / canvas.height;
|
|
8115
|
-
// Use CSS min() to pick the smaller of maxWidth or the width that corresponds to maxHeight
|
|
8116
|
-
const imageWidth = `min(${maxWidth}, calc(${maxHeight} * ${aspectRatio}))`;
|
|
8117
|
-
setCanvasStyle({ width: imageWidth });
|
|
8118
|
-
// Get full texture data, then draw only the sub-region
|
|
8119
|
-
const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, face, channels);
|
|
8120
|
-
const context = canvas.getContext("2d");
|
|
8121
|
-
if (context) {
|
|
8122
|
-
const fullImageData = context.createImageData(textureWidth, textureHeight);
|
|
8123
|
-
fullImageData.data.set(data);
|
|
8124
|
-
// Use putImageData with dirty rect to draw only the sub-region
|
|
8125
|
-
context.putImageData(fullImageData, -offsetX, -offsetY, offsetX, offsetY, canvas.width, canvas.height);
|
|
8126
|
-
}
|
|
8127
|
-
}
|
|
8128
|
-
catch {
|
|
8129
|
-
// If we fail, leave the canvas empty
|
|
8130
|
-
}
|
|
8131
|
-
}, [texture, face, channels, offsetX, offsetY, width, height, internalTexture]);
|
|
8132
|
-
useEffect(() => {
|
|
8133
|
-
void updatePreviewAsync();
|
|
8134
|
-
}, [updatePreviewAsync]);
|
|
8135
|
-
return (jsxs("div", { className: classes.root, children: [disableToolbar ? null : texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("div", { className: classes.previewContainer, children: jsx("canvas", { ref: canvasRef, className: classes.preview, style: canvasStyle }) }), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
|
|
8136
|
-
void updatePreviewAsync();
|
|
8137
|
-
}, children: "Refresh" }))] }));
|
|
8138
|
-
};
|
|
8139
8201
|
/**
|
|
8140
8202
|
* Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
|
|
8141
8203
|
* This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
|
|
@@ -8222,6 +8284,92 @@ async function ApplyChannelsToTextureDataAsync(texture, width, height, face, cha
|
|
|
8222
8284
|
return data;
|
|
8223
8285
|
}
|
|
8224
8286
|
|
|
8287
|
+
const useStyles$9 = makeStyles({
|
|
8288
|
+
root: {
|
|
8289
|
+
display: "flex",
|
|
8290
|
+
flexDirection: "column",
|
|
8291
|
+
},
|
|
8292
|
+
controls: {
|
|
8293
|
+
display: "flex",
|
|
8294
|
+
gap: tokens.spacingHorizontalXS,
|
|
8295
|
+
},
|
|
8296
|
+
controlButton: {
|
|
8297
|
+
minWidth: "auto",
|
|
8298
|
+
flex: "1 1 0", // Equal flex grow/shrink with 0 basis
|
|
8299
|
+
paddingVertical: tokens.spacingVerticalXS,
|
|
8300
|
+
paddingHorizontal: tokens.spacingHorizontalS,
|
|
8301
|
+
overflow: "hidden",
|
|
8302
|
+
textOverflow: "ellipsis",
|
|
8303
|
+
},
|
|
8304
|
+
preview: {
|
|
8305
|
+
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
8306
|
+
display: "block",
|
|
8307
|
+
objectFit: "contain",
|
|
8308
|
+
},
|
|
8309
|
+
previewContainer: {
|
|
8310
|
+
display: "flex",
|
|
8311
|
+
justifyContent: "center",
|
|
8312
|
+
marginTop: tokens.spacingVerticalXS,
|
|
8313
|
+
marginBottom: tokens.spacingVerticalS,
|
|
8314
|
+
width: "100%",
|
|
8315
|
+
},
|
|
8316
|
+
});
|
|
8317
|
+
// This method of holding TextureChannels was brought over from inspectorv1 and can likely be refactored/simplified
|
|
8318
|
+
const TextureChannelStates = {
|
|
8319
|
+
R: { R: true, G: false, B: false, A: false },
|
|
8320
|
+
G: { R: false, G: true, B: false, A: false },
|
|
8321
|
+
B: { R: false, G: false, B: true, A: false },
|
|
8322
|
+
A: { R: false, G: false, B: false, A: true },
|
|
8323
|
+
ALL: { R: true, G: true, B: true, A: true },
|
|
8324
|
+
};
|
|
8325
|
+
const TexturePreview = (props) => {
|
|
8326
|
+
const { texture, disableToolbar = false, maxWidth = "100%", maxHeight = "384px", offsetX = 0, offsetY = 0, width, height, imperativeRef } = props;
|
|
8327
|
+
const classes = useStyles$9();
|
|
8328
|
+
const canvasRef = useRef(null);
|
|
8329
|
+
const [channels, setChannels] = useState(TextureChannelStates.ALL);
|
|
8330
|
+
const [face, setFace] = useState(0);
|
|
8331
|
+
const [canvasStyle, setCanvasStyle] = useState();
|
|
8332
|
+
const internalTexture = useProperty(texture, "_texture");
|
|
8333
|
+
const { size } = useContext(ToolContext);
|
|
8334
|
+
const updatePreviewAsync = useCallback(async () => {
|
|
8335
|
+
const canvas = canvasRef.current;
|
|
8336
|
+
if (!canvas) {
|
|
8337
|
+
return;
|
|
8338
|
+
}
|
|
8339
|
+
try {
|
|
8340
|
+
await WhenTextureReadyAsync(texture); // Ensure texture is loaded before grabbing size
|
|
8341
|
+
const { width: textureWidth, height: textureHeight } = texture.getSize();
|
|
8342
|
+
// Set canvas dimensions to the sub-region size
|
|
8343
|
+
canvas.width = width ?? textureWidth;
|
|
8344
|
+
canvas.height = height ?? textureHeight;
|
|
8345
|
+
// Calculate the width that corresponds to maxHeight while maintaining aspect ratio
|
|
8346
|
+
const aspectRatio = canvas.width / canvas.height;
|
|
8347
|
+
// Use CSS min() to pick the smaller of maxWidth or the width that corresponds to maxHeight
|
|
8348
|
+
const imageWidth = `min(${maxWidth}, calc(${maxHeight} * ${aspectRatio}))`;
|
|
8349
|
+
setCanvasStyle({ width: imageWidth });
|
|
8350
|
+
// Get full texture data, then draw only the sub-region
|
|
8351
|
+
const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, face, channels);
|
|
8352
|
+
const context = canvas.getContext("2d");
|
|
8353
|
+
if (context) {
|
|
8354
|
+
const fullImageData = context.createImageData(textureWidth, textureHeight);
|
|
8355
|
+
fullImageData.data.set(data);
|
|
8356
|
+
// Use putImageData with dirty rect to draw only the sub-region
|
|
8357
|
+
context.putImageData(fullImageData, -offsetX, -offsetY, offsetX, offsetY, canvas.width, canvas.height);
|
|
8358
|
+
}
|
|
8359
|
+
}
|
|
8360
|
+
catch {
|
|
8361
|
+
// If we fail, leave the canvas empty
|
|
8362
|
+
}
|
|
8363
|
+
}, [texture, face, channels, offsetX, offsetY, width, height, internalTexture]);
|
|
8364
|
+
useImperativeHandle(imperativeRef, () => ({ refresh: updatePreviewAsync }), [updatePreviewAsync]);
|
|
8365
|
+
useEffect(() => {
|
|
8366
|
+
void updatePreviewAsync();
|
|
8367
|
+
}, [updatePreviewAsync]);
|
|
8368
|
+
return (jsxs("div", { className: classes.root, children: [disableToolbar ? null : texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("div", { className: classes.previewContainer, children: jsx("canvas", { ref: canvasRef, className: classes.preview, style: canvasStyle }) }), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
|
|
8369
|
+
void updatePreviewAsync();
|
|
8370
|
+
}, children: "Refresh" }))] }));
|
|
8371
|
+
};
|
|
8372
|
+
|
|
8225
8373
|
function useMaxCellCount(sprite) {
|
|
8226
8374
|
const manager = sprite.manager;
|
|
8227
8375
|
const texture = useProperty(manager, "texture");
|
|
@@ -8434,8 +8582,11 @@ function FindTextureType(type) {
|
|
|
8434
8582
|
}
|
|
8435
8583
|
|
|
8436
8584
|
const BaseTexturePreviewProperties = (props) => {
|
|
8437
|
-
|
|
8438
|
-
|
|
8585
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
8586
|
+
const { texture, textureEditor: TextureEditor } = props;
|
|
8587
|
+
const texturePreviewImperativeRef = useRef(null);
|
|
8588
|
+
const childWindow = useRef(null);
|
|
8589
|
+
return (jsxs(Fragment, { children: [jsx(TexturePreview, { imperativeRef: texturePreviewImperativeRef, texture: texture }), jsx(TextureUpload, { texture: texture }), jsx(ButtonLine, { label: "Edit Texture", onClick: () => childWindow.current?.open() }), jsx(ChildWindow, { id: "Texture Editor", imperativeRef: childWindow, children: jsx(TextureEditor, { texture: texture, onUpdate: async () => await texturePreviewImperativeRef.current?.refresh() }) })] }));
|
|
8439
8590
|
};
|
|
8440
8591
|
const BaseTextureGeneralProperties = (props) => {
|
|
8441
8592
|
const { texture } = props;
|
|
@@ -8539,21 +8690,1621 @@ const ThinTextureSamplingProperties = (props) => {
|
|
|
8539
8690
|
return jsx(NumberDropdownPropertyLine, { label: "Sampling", value: samplingMode, options: SamplingMode, onChange: (value) => texture.updateSamplingMode(value) });
|
|
8540
8691
|
};
|
|
8541
8692
|
|
|
8542
|
-
//
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8693
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
8694
|
+
const canvasShader = {
|
|
8695
|
+
path: {
|
|
8696
|
+
vertexSource: `
|
|
8697
|
+
precision highp float;
|
|
8698
|
+
|
|
8699
|
+
attribute vec3 position;
|
|
8700
|
+
attribute vec2 uv;
|
|
8701
|
+
|
|
8702
|
+
uniform mat4 worldViewProjection;
|
|
8703
|
+
|
|
8704
|
+
varying vec2 vUV;
|
|
8705
|
+
|
|
8706
|
+
void main(void) {
|
|
8707
|
+
gl_Position = worldViewProjection * vec4(position, 1.0);
|
|
8708
|
+
vUV = uv;
|
|
8709
|
+
}
|
|
8710
|
+
`,
|
|
8711
|
+
fragmentSource: `
|
|
8712
|
+
precision highp float;
|
|
8713
|
+
|
|
8714
|
+
uniform sampler2D textureSampler;
|
|
8715
|
+
|
|
8716
|
+
uniform bool r;
|
|
8717
|
+
uniform bool g;
|
|
8718
|
+
uniform bool b;
|
|
8719
|
+
uniform bool a;
|
|
8720
|
+
|
|
8721
|
+
uniform int x1;
|
|
8722
|
+
uniform int y1;
|
|
8723
|
+
uniform int x2;
|
|
8724
|
+
uniform int y2;
|
|
8725
|
+
uniform int w;
|
|
8726
|
+
uniform int h;
|
|
8727
|
+
|
|
8728
|
+
uniform int time;
|
|
8729
|
+
uniform bool showGrid;
|
|
8730
|
+
|
|
8731
|
+
varying vec2 vUV;
|
|
8732
|
+
|
|
8733
|
+
float scl = 200.0;
|
|
8734
|
+
float speed = 10.0 / 1000.0;
|
|
8735
|
+
float smoothing = 0.2;
|
|
8736
|
+
|
|
8737
|
+
void main(void) {
|
|
8738
|
+
vec2 pos2 = vec2(gl_FragCoord.x, gl_FragCoord.y);
|
|
8739
|
+
vec2 pos = floor(pos2 * 0.05);
|
|
8740
|
+
float pattern = mod(pos.x + pos.y, 2.0);
|
|
8741
|
+
if (pattern == 0.0) {
|
|
8742
|
+
pattern = 0.7;
|
|
8743
|
+
}
|
|
8744
|
+
vec4 bg = vec4(pattern, pattern, pattern, 1.0);
|
|
8745
|
+
vec4 col = texture(textureSampler, vUV);
|
|
8746
|
+
if (!r && !g && !b) {
|
|
8747
|
+
if (a) {
|
|
8748
|
+
col = vec4(col.a, col.a, col.a, 1.0);
|
|
8749
|
+
} else {
|
|
8750
|
+
col = vec4(0.0,0.0,0.0,0.0);
|
|
8751
|
+
}
|
|
8752
|
+
} else {
|
|
8753
|
+
if (!r) {
|
|
8754
|
+
col.r = 0.0;
|
|
8755
|
+
if (!b) {
|
|
8756
|
+
col.r = col.g;
|
|
8757
|
+
}
|
|
8758
|
+
else if (!g) {
|
|
8759
|
+
col.r = col.b;
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
8762
|
+
if (!g) {
|
|
8763
|
+
col.g = 0.0;
|
|
8764
|
+
if (!b) {
|
|
8765
|
+
col.g = col.r;
|
|
8766
|
+
}
|
|
8767
|
+
else if (!r) {
|
|
8768
|
+
col.g = col.b;
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
if (!b) {
|
|
8772
|
+
col.b = 0.0;
|
|
8773
|
+
if (!r) {
|
|
8774
|
+
col.b = col.g;
|
|
8775
|
+
} else if (!g) {
|
|
8776
|
+
col.b = col.r;
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
if (!a) {
|
|
8780
|
+
col.a = 1.0;
|
|
8781
|
+
}
|
|
8782
|
+
}
|
|
8783
|
+
gl_FragColor = col * (col.a) + bg * (1.0 - col.a);
|
|
8784
|
+
float wF = float(w);
|
|
8785
|
+
float hF = float(h);
|
|
8786
|
+
int xPixel = int(floor(vUV.x * wF));
|
|
8787
|
+
int yPixel = int(floor((1.0 - vUV.y) * hF));
|
|
8788
|
+
int xDis = min(abs(xPixel - x1), abs(xPixel - x2));
|
|
8789
|
+
int yDis = min(abs(yPixel - y1), abs(yPixel - y2));
|
|
8790
|
+
if (showGrid) {
|
|
8791
|
+
vec2 frac = fract(vUV * vec2(wF,hF));
|
|
8792
|
+
float thickness = 0.1;
|
|
8793
|
+
if (abs(frac.x) < thickness || abs (frac.y) < thickness) {
|
|
8794
|
+
gl_FragColor = vec4(0.75,0.75,0.75,1.0);
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
if (xPixel >= x1 && yPixel >= y1 && xPixel <= x2 && yPixel <= y2) {
|
|
8798
|
+
if (xDis <= 4 || yDis <= 4) {
|
|
8799
|
+
float c = sin(vUV.x * scl + vUV.y * scl + float(time) * speed);
|
|
8800
|
+
c = smoothstep(-smoothing,smoothing,c);
|
|
8801
|
+
float val = 1.0 - c;
|
|
8802
|
+
gl_FragColor = vec4(val, val, val, 1.0) * 0.7 + gl_FragColor * 0.3;
|
|
8803
|
+
}
|
|
8804
|
+
}
|
|
8805
|
+
}`,
|
|
8806
|
+
},
|
|
8807
|
+
options: {
|
|
8808
|
+
attributes: ["position", "uv"],
|
|
8809
|
+
uniforms: ["worldViewProjection", "textureSampler", "r", "g", "b", "a", "x1", "y1", "x2", "y2", "w", "h", "time", "showGrid"],
|
|
8810
|
+
},
|
|
8811
|
+
};
|
|
8812
|
+
|
|
8813
|
+
class TextureCanvasManager {
|
|
8814
|
+
constructor(texture, window, canvasUI, canvas2D, canvas3D, setPixelData, metadata, onUpdate, setMetadata, setMipLevel) {
|
|
8815
|
+
this._isPanning = false;
|
|
8816
|
+
this._mouseX = 0;
|
|
8817
|
+
this._mouseY = 0;
|
|
8818
|
+
this._size = { width: 0, height: 0 };
|
|
8819
|
+
this._channels = [];
|
|
8820
|
+
this._face = 0;
|
|
8821
|
+
this._mipLevel = 0;
|
|
8822
|
+
/** This is a hidden texture which is only responsible for holding the actual texture memory in the original engine */
|
|
8823
|
+
this._target = null;
|
|
8824
|
+
/** Keeps track of whether we have modified the texture */
|
|
8825
|
+
this._didEdit = false;
|
|
8826
|
+
this._plane = null;
|
|
8827
|
+
/** Tracks which keys are currently pressed */
|
|
8828
|
+
this._keyMap = new Map();
|
|
8829
|
+
/** Tracks which mouse buttons are currently pressed */
|
|
8830
|
+
this._buttonsPressed = 0;
|
|
8831
|
+
this.ZOOM_MOUSE_SPEED = 0.001;
|
|
8832
|
+
this.ZOOM_KEYBOARD_SPEED = 0.4;
|
|
8833
|
+
this.ZOOM_IN_KEY = "+";
|
|
8834
|
+
this.ZOOM_OUT_KEY = "-";
|
|
8835
|
+
this.PAN_SPEED = 0.003;
|
|
8836
|
+
this.PAN_KEY = "Space";
|
|
8837
|
+
this.MIN_SCALE = 0.01;
|
|
8838
|
+
this.GRID_SCALE = 0.047;
|
|
8839
|
+
this.MAX_SCALE = 10;
|
|
8840
|
+
this.SELECT_ALL_KEY = "KeyA";
|
|
8841
|
+
this.SAVE_KEY = "KeyS";
|
|
8842
|
+
this.RESET_KEY = "KeyR";
|
|
8843
|
+
this.DESELECT_KEY = "Escape";
|
|
8844
|
+
/** The number of milliseconds between texture updates */
|
|
8845
|
+
this.PUSH_FREQUENCY = 32;
|
|
8846
|
+
this._tool = null;
|
|
8847
|
+
this._editing3D = false;
|
|
8848
|
+
this._imageData = null;
|
|
8849
|
+
this._canPush = true;
|
|
8850
|
+
this._shouldPush = false;
|
|
8851
|
+
this._window = window;
|
|
8852
|
+
this._uiCanvas = canvasUI;
|
|
8853
|
+
this._2DCanvas = canvas2D;
|
|
8854
|
+
this._3DCanvas = canvas3D;
|
|
8855
|
+
this._paintCanvas = document.createElement("canvas");
|
|
8856
|
+
this._setPixelData = setPixelData;
|
|
8857
|
+
this._metadata = metadata;
|
|
8858
|
+
this._onUpdate = onUpdate;
|
|
8859
|
+
this._setMetadata = setMetadata;
|
|
8860
|
+
this._setMipLevel = setMipLevel;
|
|
8861
|
+
this._originalTexture = texture;
|
|
8862
|
+
this._originalTextureProperties = {
|
|
8863
|
+
_texture: this._originalTexture._texture,
|
|
8864
|
+
url: this._originalTexture.url,
|
|
8865
|
+
_forceSerialize: this._originalTexture._forceSerialize,
|
|
8866
|
+
};
|
|
8867
|
+
this._engine = new Engine(this._uiCanvas, true);
|
|
8868
|
+
this._scene = new Scene(this._engine, { virtual: true });
|
|
8869
|
+
this._scene.clearColor = new Color4(0, 0, 0, 0);
|
|
8870
|
+
this._camera = new FreeCamera("camera", new Vector3(0, 0, -1), this._scene);
|
|
8871
|
+
this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
|
|
8872
|
+
this._camera.minZ = 0.5;
|
|
8873
|
+
this._camera.maxZ = 1.5;
|
|
8874
|
+
this._cameraPos = new Vector2();
|
|
8875
|
+
this.setSize(texture.getSize());
|
|
8876
|
+
this._channelsTexture = new HtmlElementTexture("ct", this._2DCanvas, {
|
|
8877
|
+
engine: this._engine,
|
|
8878
|
+
scene: null,
|
|
8879
|
+
samplingMode: Texture.NEAREST_SAMPLINGMODE,
|
|
8880
|
+
generateMipMaps: true,
|
|
8881
|
+
});
|
|
8882
|
+
this._3DEngine = new Engine(this._3DCanvas);
|
|
8883
|
+
this._3DScene = new Scene(this._3DEngine, { virtual: true });
|
|
8884
|
+
this._3DScene.clearColor = new Color4(0, 0, 0, 0);
|
|
8885
|
+
this._3DCanvasTexture = new HtmlElementTexture("canvas", this._2DCanvas, { engine: this._3DEngine, scene: this._3DScene });
|
|
8886
|
+
this._3DCanvasTexture.hasAlpha = true;
|
|
8887
|
+
const cam = new FreeCamera("camera", new Vector3(0, 0, -1), this._3DScene);
|
|
8888
|
+
cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
|
|
8889
|
+
[cam.orthoBottom, cam.orthoLeft, cam.orthoTop, cam.orthoRight] = [-0.5, -0.5, 0.5, 0.5];
|
|
8890
|
+
this._3DPlane = CreatePlane("texture", { width: 1, height: 1 }, this._3DScene);
|
|
8891
|
+
this._3DPlane.hasVertexAlpha = true;
|
|
8892
|
+
const mat = new StandardMaterial("material", this._3DScene);
|
|
8893
|
+
mat.diffuseTexture = this._3DCanvasTexture;
|
|
8894
|
+
mat.useAlphaFromDiffuseTexture = true;
|
|
8895
|
+
mat.disableLighting = true;
|
|
8896
|
+
mat.emissiveColor = Color3.White();
|
|
8897
|
+
this._3DPlane.material = mat;
|
|
8898
|
+
this._planeMaterial = new ShaderMaterial("canvasShader", this._scene, canvasShader.path, canvasShader.options);
|
|
8899
|
+
this.grabOriginalTexture();
|
|
8900
|
+
this._planeMaterial.setTexture("textureSampler", this._channelsTexture);
|
|
8901
|
+
this._planeMaterial.setFloat("r", 1.0);
|
|
8902
|
+
this._planeMaterial.setFloat("g", 1.0);
|
|
8903
|
+
this._planeMaterial.setFloat("b", 1.0);
|
|
8904
|
+
this._planeMaterial.setFloat("a", 1.0);
|
|
8905
|
+
this._planeMaterial.setInt("x1", -1);
|
|
8906
|
+
this._planeMaterial.setInt("y1", -1);
|
|
8907
|
+
this._planeMaterial.setInt("x2", -1);
|
|
8908
|
+
this._planeMaterial.setInt("y2", -1);
|
|
8909
|
+
this._planeMaterial.setInt("w", this._size.width);
|
|
8910
|
+
this._planeMaterial.setInt("h", this._size.height);
|
|
8911
|
+
this._planeMaterial.setInt("time", 0);
|
|
8912
|
+
this._planeMaterial.setFloat("showGrid", 0.0);
|
|
8913
|
+
if (this._plane) {
|
|
8914
|
+
this._plane.material = this._planeMaterial;
|
|
8915
|
+
}
|
|
8916
|
+
this._window.addEventListener("keydown", (evt) => {
|
|
8917
|
+
this._keyMap.set(evt.code, true);
|
|
8918
|
+
if (evt.code === this.SELECT_ALL_KEY && evt.ctrlKey) {
|
|
8919
|
+
this._setMetadata({
|
|
8920
|
+
select: {
|
|
8921
|
+
x1: 0,
|
|
8922
|
+
y1: 0,
|
|
8923
|
+
x2: this._size.width,
|
|
8924
|
+
y2: this._size.height,
|
|
8925
|
+
},
|
|
8926
|
+
});
|
|
8927
|
+
evt.preventDefault();
|
|
8928
|
+
}
|
|
8929
|
+
if (evt.code === this.SAVE_KEY && evt.ctrlKey) {
|
|
8930
|
+
this.saveTexture();
|
|
8931
|
+
evt.preventDefault();
|
|
8932
|
+
}
|
|
8933
|
+
if (evt.code === this.RESET_KEY && evt.ctrlKey) {
|
|
8934
|
+
this.reset();
|
|
8935
|
+
evt.preventDefault();
|
|
8936
|
+
}
|
|
8937
|
+
if (evt.code === this.DESELECT_KEY) {
|
|
8938
|
+
this._setMetadata({
|
|
8939
|
+
select: {
|
|
8940
|
+
x1: -1,
|
|
8941
|
+
y1: -1,
|
|
8942
|
+
x2: -1,
|
|
8943
|
+
y2: -1,
|
|
8944
|
+
},
|
|
8945
|
+
});
|
|
8946
|
+
}
|
|
8947
|
+
});
|
|
8948
|
+
this._window.addEventListener("keyup", (evt) => {
|
|
8949
|
+
this._keyMap.set(evt.code, false);
|
|
8950
|
+
});
|
|
8951
|
+
this._engine.runRenderLoop(() => {
|
|
8952
|
+
this._engine.resize();
|
|
8953
|
+
this._scene.render();
|
|
8954
|
+
this._planeMaterial.setInt("time", new Date().getTime());
|
|
8955
|
+
});
|
|
8956
|
+
this._scale = 1.5 / Math.max(this._size.width, this._size.height);
|
|
8957
|
+
this._isPanning = false;
|
|
8958
|
+
this._scene.onBeforeRenderObservable.add(() => {
|
|
8959
|
+
this._scale = Math.min(Math.max(this._scale, this.MIN_SCALE / Math.log2(Math.min(this._size.width, this._size.height))), this.MAX_SCALE);
|
|
8960
|
+
if (this._scale > this.GRID_SCALE) {
|
|
8961
|
+
this._planeMaterial.setFloat("showGrid", 1.0);
|
|
8962
|
+
}
|
|
8963
|
+
else {
|
|
8964
|
+
this._planeMaterial.setFloat("showGrid", 0.0);
|
|
8965
|
+
}
|
|
8966
|
+
const ratio = this._uiCanvas?.width / this._uiCanvas?.height;
|
|
8967
|
+
const { x, y } = this._cameraPos;
|
|
8968
|
+
this._camera.orthoBottom = y - 1 / this._scale;
|
|
8969
|
+
this._camera.orthoTop = y + 1 / this._scale;
|
|
8970
|
+
this._camera.orthoLeft = x - ratio / this._scale;
|
|
8971
|
+
this._camera.orthoRight = x + ratio / this._scale;
|
|
8972
|
+
});
|
|
8973
|
+
this._scene.onPointerObservable.add((pointerInfo) => {
|
|
8974
|
+
const leftButtonPressed = pointerInfo.event.buttons & 1;
|
|
8975
|
+
const middleButtonPressed = pointerInfo.event.buttons & 4;
|
|
8976
|
+
if (!this._isPanning) {
|
|
8977
|
+
if ((leftButtonPressed && !(this._buttonsPressed & 1) && this._keyMap.get(this.PAN_KEY)) || middleButtonPressed) {
|
|
8978
|
+
this._isPanning = true;
|
|
8979
|
+
this._mouseX = pointerInfo.event.x;
|
|
8980
|
+
this._mouseY = pointerInfo.event.y;
|
|
8981
|
+
}
|
|
8982
|
+
if (middleButtonPressed) {
|
|
8983
|
+
this._isPanning = true;
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
else if ((!leftButtonPressed || !this._keyMap.get(this.PAN_KEY)) && !middleButtonPressed) {
|
|
8987
|
+
this._isPanning = false;
|
|
8988
|
+
}
|
|
8989
|
+
switch (pointerInfo.type) {
|
|
8990
|
+
case PointerEventTypes.POINTERWHEEL: {
|
|
8991
|
+
const event = pointerInfo.event;
|
|
8992
|
+
this._scale -= event.deltaY * this.ZOOM_MOUSE_SPEED * this._scale;
|
|
8993
|
+
break;
|
|
8994
|
+
}
|
|
8995
|
+
case PointerEventTypes.POINTERMOVE:
|
|
8996
|
+
if (this._isPanning) {
|
|
8997
|
+
this._cameraPos.x -= ((pointerInfo.event.x - this._mouseX) * this.PAN_SPEED) / this._scale;
|
|
8998
|
+
this._cameraPos.y += ((pointerInfo.event.y - this._mouseY) * this.PAN_SPEED) / this._scale;
|
|
8999
|
+
this._mouseX = pointerInfo.event.x;
|
|
9000
|
+
this._mouseY = pointerInfo.event.y;
|
|
9001
|
+
}
|
|
9002
|
+
if (pointerInfo.pickInfo?.hit) {
|
|
9003
|
+
if (this._imageData) {
|
|
9004
|
+
const pos = this.getMouseCoordinates(pointerInfo);
|
|
9005
|
+
const idx = (pos.x + pos.y * this._size.width) * 4;
|
|
9006
|
+
this._setPixelData({
|
|
9007
|
+
x: pos.x,
|
|
9008
|
+
y: pos.y,
|
|
9009
|
+
r: this._imageData[idx],
|
|
9010
|
+
g: this._imageData[idx + 1],
|
|
9011
|
+
b: this._imageData[idx + 2],
|
|
9012
|
+
a: this._imageData[idx + 3],
|
|
9013
|
+
});
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
9016
|
+
else {
|
|
9017
|
+
this._setPixelData({});
|
|
9018
|
+
}
|
|
9019
|
+
break;
|
|
9020
|
+
}
|
|
9021
|
+
this._buttonsPressed = pointerInfo.event.buttons;
|
|
9022
|
+
});
|
|
9023
|
+
this._scene.onKeyboardObservable.add((kbInfo) => {
|
|
9024
|
+
switch (kbInfo.type) {
|
|
9025
|
+
case KeyboardEventTypes.KEYDOWN:
|
|
9026
|
+
this._keyMap.set(kbInfo.event.key, true);
|
|
9027
|
+
switch (kbInfo.event.key) {
|
|
9028
|
+
case this.ZOOM_IN_KEY:
|
|
9029
|
+
this._scale += this.ZOOM_KEYBOARD_SPEED * this._scale;
|
|
9030
|
+
break;
|
|
9031
|
+
case this.ZOOM_OUT_KEY:
|
|
9032
|
+
this._scale -= this.ZOOM_KEYBOARD_SPEED * this._scale;
|
|
9033
|
+
break;
|
|
9034
|
+
}
|
|
9035
|
+
break;
|
|
9036
|
+
case KeyboardEventTypes.KEYUP:
|
|
9037
|
+
this._keyMap.set(kbInfo.event.key, false);
|
|
9038
|
+
break;
|
|
9039
|
+
}
|
|
9040
|
+
});
|
|
9041
|
+
}
|
|
9042
|
+
async updateTexture() {
|
|
9043
|
+
if (this._mipLevel !== 0) {
|
|
9044
|
+
this._setMipLevel(0);
|
|
9045
|
+
}
|
|
9046
|
+
this._didEdit = true;
|
|
9047
|
+
const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
|
|
9048
|
+
if (this._editing3D) {
|
|
9049
|
+
this._3DCanvasTexture.update();
|
|
9050
|
+
this._3DScene.render();
|
|
9051
|
+
}
|
|
9052
|
+
if (this._originalTexture.isCube) ;
|
|
9053
|
+
else {
|
|
9054
|
+
if (!this._target) {
|
|
9055
|
+
this._target = new HtmlElementTexture("editor", element, {
|
|
9056
|
+
engine: this._originalTexture.getScene()?.getEngine(),
|
|
9057
|
+
scene: null,
|
|
9058
|
+
samplingMode: this._originalTexture.samplingMode,
|
|
9059
|
+
generateMipMaps: this._originalTextureProperties._texture?.generateMipMaps,
|
|
9060
|
+
});
|
|
9061
|
+
}
|
|
9062
|
+
else {
|
|
9063
|
+
this._target.element = element;
|
|
9064
|
+
}
|
|
9065
|
+
this.pushTexture();
|
|
9066
|
+
}
|
|
9067
|
+
this._originalTexture._texture = this._target?._texture ?? null;
|
|
9068
|
+
this._originalTexture.url = null;
|
|
9069
|
+
this._originalTexture._forceSerialize = true;
|
|
9070
|
+
this._channelsTexture.element = element;
|
|
9071
|
+
this.updateDisplay();
|
|
9072
|
+
this._onUpdate();
|
|
9073
|
+
}
|
|
9074
|
+
async pushTexture() {
|
|
9075
|
+
if (this._canPush && this._target) {
|
|
9076
|
+
const invertY = this._target.constructor.name === HtmlElementTexture.name ? false : this._originalTexture.invertY;
|
|
9077
|
+
this._target.update(invertY);
|
|
9078
|
+
this._target._texture?.updateSize(this._size.width, this._size.height);
|
|
9079
|
+
if (this._editing3D) {
|
|
9080
|
+
const bufferView = await this._3DEngine.readPixels(0, 0, this._size.width, this._size.height);
|
|
9081
|
+
this._imageData = new Uint8Array(bufferView.buffer, 0, bufferView.byteLength);
|
|
9082
|
+
}
|
|
9083
|
+
else {
|
|
9084
|
+
this._imageData = this._2DCanvas.getContext("2d").getImageData(0, 0, this._size.width, this._size.height).data;
|
|
9085
|
+
}
|
|
9086
|
+
this._canPush = false;
|
|
9087
|
+
this._shouldPush = false;
|
|
9088
|
+
setTimeout(() => {
|
|
9089
|
+
this._canPush = true;
|
|
9090
|
+
if (this._shouldPush) {
|
|
9091
|
+
this.pushTexture();
|
|
9092
|
+
}
|
|
9093
|
+
}, this.PUSH_FREQUENCY);
|
|
9094
|
+
}
|
|
9095
|
+
else {
|
|
9096
|
+
this._shouldPush = true;
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
async startPainting() {
|
|
9100
|
+
if (this._mipLevel != 0) {
|
|
9101
|
+
this._setMipLevel(0);
|
|
9102
|
+
}
|
|
9103
|
+
let x = 0, y = 0, w = this._size.width, h = this._size.height;
|
|
9104
|
+
if (this._metadata.select.x1 != -1) {
|
|
9105
|
+
x = this._metadata.select.x1;
|
|
9106
|
+
y = this._metadata.select.y1;
|
|
9107
|
+
w = this._metadata.select.x2 - this._metadata.select.x1;
|
|
9108
|
+
h = this._metadata.select.y2 - this._metadata.select.y1;
|
|
9109
|
+
}
|
|
9110
|
+
this._paintCanvas.width = w;
|
|
9111
|
+
this._paintCanvas.height = h;
|
|
9112
|
+
const ctx = this._paintCanvas.getContext("2d");
|
|
9113
|
+
ctx.imageSmoothingEnabled = false;
|
|
9114
|
+
ctx.drawImage(this._2DCanvas, x, y, w, h, 0, 0, w, h);
|
|
9115
|
+
return ctx;
|
|
9116
|
+
}
|
|
9117
|
+
updatePainting() {
|
|
9118
|
+
let x = 0, y = 0, w = this._size.width, h = this._size.height;
|
|
9119
|
+
if (this._metadata.select.x1 != -1) {
|
|
9120
|
+
x = this._metadata.select.x1;
|
|
9121
|
+
y = this._metadata.select.y1;
|
|
9122
|
+
w = this._metadata.select.x2 - this._metadata.select.x1;
|
|
9123
|
+
h = this._metadata.select.y2 - this._metadata.select.y1;
|
|
9124
|
+
}
|
|
9125
|
+
let editingAllChannels = true;
|
|
9126
|
+
for (const channel of this._channels) {
|
|
9127
|
+
if (!channel.editable) {
|
|
9128
|
+
editingAllChannels = false;
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
let oldData;
|
|
9132
|
+
if (!editingAllChannels) {
|
|
9133
|
+
oldData = this._2DCanvas.getContext("2d").getImageData(x, y, w, h).data;
|
|
9134
|
+
}
|
|
9135
|
+
const ctx = this._paintCanvas.getContext("2d");
|
|
9136
|
+
const ctx2D = this.canvas2D.getContext("2d");
|
|
9137
|
+
ctx2D.globalAlpha = 1.0;
|
|
9138
|
+
ctx2D.globalCompositeOperation = "destination-out";
|
|
9139
|
+
ctx2D.fillStyle = "white";
|
|
9140
|
+
ctx2D.fillRect(x, y, w, h);
|
|
9141
|
+
ctx2D.imageSmoothingEnabled = false;
|
|
9142
|
+
// If we're not editing all channels, we must process the pixel data
|
|
9143
|
+
if (!editingAllChannels) {
|
|
9144
|
+
const newData = ctx.getImageData(0, 0, w, h);
|
|
9145
|
+
const nd = newData.data;
|
|
9146
|
+
for (let index = 0; index < this._channels.length; index++) {
|
|
9147
|
+
const channel = this._channels[index];
|
|
9148
|
+
if (!channel.editable) {
|
|
9149
|
+
for (let i = index; i < w * h * 4; i += 4) {
|
|
9150
|
+
nd[i] = oldData[i];
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
}
|
|
9154
|
+
ctx2D.globalCompositeOperation = "source-over";
|
|
9155
|
+
ctx2D.globalAlpha = 1.0;
|
|
9156
|
+
ctx2D.putImageData(newData, x, y);
|
|
9157
|
+
}
|
|
9158
|
+
else {
|
|
9159
|
+
ctx2D.globalCompositeOperation = "source-over";
|
|
9160
|
+
ctx2D.globalAlpha = 1.0;
|
|
9161
|
+
// We want to use drawImage wherever possible since it is much faster than putImageData
|
|
9162
|
+
ctx2D.drawImage(ctx.canvas, x, y);
|
|
9163
|
+
}
|
|
9164
|
+
this.updateTexture();
|
|
9165
|
+
}
|
|
9166
|
+
stopPainting() {
|
|
9167
|
+
this._paintCanvas.getContext("2d").clearRect(0, 0, this._paintCanvas.width, this._paintCanvas.height);
|
|
9168
|
+
}
|
|
9169
|
+
updateDisplay() {
|
|
9170
|
+
this._3DScene.render();
|
|
9171
|
+
this._channelsTexture.update(true);
|
|
9172
|
+
}
|
|
9173
|
+
set channels(channels) {
|
|
9174
|
+
// Determine if we need to re-render the texture. This is an expensive operation, so we should only do it if channel visibility has changed.
|
|
9175
|
+
let needsRender = false;
|
|
9176
|
+
if (channels.length !== this._channels.length) {
|
|
9177
|
+
needsRender = true;
|
|
9178
|
+
}
|
|
9179
|
+
else {
|
|
9180
|
+
for (let i = 0; i < channels.length; i++) {
|
|
9181
|
+
const channel = channels[i];
|
|
9182
|
+
if (channel.visible !== this._channels[i].visible) {
|
|
9183
|
+
needsRender = true;
|
|
9184
|
+
this._planeMaterial.setFloat(channel.id.toLowerCase(), channel.visible ? 1.0 : 0.0);
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
this._channels = channels;
|
|
9189
|
+
if (needsRender) {
|
|
9190
|
+
this.updateDisplay();
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
9193
|
+
paintPixelsOnCanvas(pixelData, canvas) {
|
|
9194
|
+
const ctx = canvas.getContext("2d");
|
|
9195
|
+
const imgData = ctx.createImageData(canvas.width, canvas.height);
|
|
9196
|
+
imgData.data.set(pixelData);
|
|
9197
|
+
ctx.putImageData(imgData, 0, 0);
|
|
9198
|
+
}
|
|
9199
|
+
async grabOriginalTexture() {
|
|
9200
|
+
// Grab image data from original texture and paint it onto the context of a DynamicTexture
|
|
9201
|
+
this.setSize(this._originalTexture.getSize());
|
|
9202
|
+
const data = await ApplyChannelsToTextureDataAsync(this._originalTexture, this._size.width, this._size.height, this._face, { R: true, G: true, B: true, A: true }, this._mipLevel);
|
|
9203
|
+
this._imageData = data;
|
|
9204
|
+
this.paintPixelsOnCanvas(data, this._2DCanvas);
|
|
9205
|
+
this._3DCanvasTexture.update();
|
|
9206
|
+
this.updateDisplay();
|
|
9207
|
+
return data;
|
|
9208
|
+
}
|
|
9209
|
+
getMouseCoordinates(pointerInfo) {
|
|
9210
|
+
if (pointerInfo.pickInfo?.hit) {
|
|
9211
|
+
const x = Math.floor(pointerInfo.pickInfo.getTextureCoordinates().x * this._size.width);
|
|
9212
|
+
const y = Math.floor((1 - pointerInfo.pickInfo.getTextureCoordinates().y) * this._size.height);
|
|
9213
|
+
return new Vector2(x, y);
|
|
9214
|
+
}
|
|
9215
|
+
else {
|
|
9216
|
+
return new Vector2();
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
get scene() {
|
|
9220
|
+
return this._scene;
|
|
9221
|
+
}
|
|
9222
|
+
get canvas2D() {
|
|
9223
|
+
return this._2DCanvas;
|
|
9224
|
+
}
|
|
9225
|
+
get size() {
|
|
9226
|
+
return this._size;
|
|
9227
|
+
}
|
|
9228
|
+
set tool(tool) {
|
|
9229
|
+
if (this._tool) {
|
|
9230
|
+
this._tool.deactivate();
|
|
9231
|
+
}
|
|
9232
|
+
this._tool = tool;
|
|
9233
|
+
if (this._tool) {
|
|
9234
|
+
this._tool.activate();
|
|
9235
|
+
if (this._editing3D && !this._tool.is3D) {
|
|
9236
|
+
this._editing3D = false;
|
|
9237
|
+
this._2DCanvas.getContext("2d")?.drawImage(this._3DCanvas, 0, 0);
|
|
9238
|
+
}
|
|
9239
|
+
else if (!this._editing3D && this._tool.is3D) {
|
|
9240
|
+
this._editing3D = true;
|
|
9241
|
+
this.updateTexture();
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
9245
|
+
get tool() {
|
|
9246
|
+
return this._tool;
|
|
9247
|
+
}
|
|
9248
|
+
set face(face) {
|
|
9249
|
+
if (this._face !== face) {
|
|
9250
|
+
this._face = face;
|
|
9251
|
+
this.grabOriginalTexture();
|
|
9252
|
+
this.updateDisplay();
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
set mipLevel(mipLevel) {
|
|
9256
|
+
if (this._mipLevel === mipLevel) {
|
|
9257
|
+
return;
|
|
9258
|
+
}
|
|
9259
|
+
this._mipLevel = mipLevel;
|
|
9260
|
+
this.grabOriginalTexture();
|
|
9261
|
+
}
|
|
9262
|
+
/** Returns the 3D scene used for postprocesses */
|
|
9263
|
+
get scene3D() {
|
|
9264
|
+
return this._3DScene;
|
|
9265
|
+
}
|
|
9266
|
+
set metadata(metadata) {
|
|
9267
|
+
this._metadata = metadata;
|
|
9268
|
+
const { x1, y1, x2, y2 } = metadata.select;
|
|
9269
|
+
this._planeMaterial.setInt("x1", x1);
|
|
9270
|
+
this._planeMaterial.setInt("y1", y1);
|
|
9271
|
+
this._planeMaterial.setInt("x2", x2);
|
|
9272
|
+
this._planeMaterial.setInt("y2", y2);
|
|
9273
|
+
}
|
|
9274
|
+
makePlane() {
|
|
9275
|
+
if (this._plane) {
|
|
9276
|
+
this._plane.dispose();
|
|
9277
|
+
}
|
|
9278
|
+
this._plane = CreatePlane("plane", { width: this._size.width, height: this._size.height }, this._scene);
|
|
9279
|
+
this._plane.enableEdgesRendering();
|
|
9280
|
+
this._plane.edgesWidth = 4.0;
|
|
9281
|
+
this._plane.edgesColor = new Color4(1, 1, 1, 1);
|
|
9282
|
+
this._plane.enablePointerMoveEvents = true;
|
|
9283
|
+
this._plane.material = this._planeMaterial;
|
|
9284
|
+
}
|
|
9285
|
+
reset() {
|
|
9286
|
+
if (this._tool && this._tool.reset) {
|
|
9287
|
+
this._tool.reset();
|
|
9288
|
+
}
|
|
9289
|
+
this._originalTexture._texture = this._originalTextureProperties._texture;
|
|
9290
|
+
this._originalTexture.url = this._originalTextureProperties.url;
|
|
9291
|
+
this._originalTexture._forceSerialize = this._originalTextureProperties._forceSerialize;
|
|
9292
|
+
this.grabOriginalTexture();
|
|
9293
|
+
this.makePlane();
|
|
9294
|
+
this._didEdit = false;
|
|
9295
|
+
this._onUpdate();
|
|
9296
|
+
}
|
|
9297
|
+
async resize(newSize) {
|
|
9298
|
+
const data = await ApplyChannelsToTextureDataAsync(this._originalTexture, newSize.width, newSize.height, this._face, { R: true, G: true, B: true, A: true });
|
|
9299
|
+
this.setSize(newSize);
|
|
9300
|
+
this.paintPixelsOnCanvas(data, this._2DCanvas);
|
|
9301
|
+
this.updateTexture();
|
|
9302
|
+
this._didEdit = true;
|
|
9303
|
+
}
|
|
9304
|
+
setSize(size) {
|
|
9305
|
+
const oldSize = this._size;
|
|
9306
|
+
this._size = size;
|
|
9307
|
+
this._2DCanvas.width = this._size.width;
|
|
9308
|
+
this._2DCanvas.height = this._size.height;
|
|
9309
|
+
this._3DCanvas.width = this._size.width;
|
|
9310
|
+
this._3DCanvas.height = this._size.height;
|
|
9311
|
+
if (this._planeMaterial) {
|
|
9312
|
+
this._planeMaterial.setInt("w", this._size.width);
|
|
9313
|
+
this._planeMaterial.setInt("h", this._size.height);
|
|
9314
|
+
}
|
|
9315
|
+
if (!oldSize || oldSize.width != size.width || oldSize.height != size.height) {
|
|
9316
|
+
this._cameraPos.x = 0;
|
|
9317
|
+
this._cameraPos.y = 0;
|
|
9318
|
+
this._scale = 1.5 / Math.max(this._size.width, this._size.height);
|
|
9319
|
+
}
|
|
9320
|
+
this.makePlane();
|
|
9321
|
+
}
|
|
9322
|
+
upload(file) {
|
|
9323
|
+
Tools.ReadFile(file, (data) => {
|
|
9324
|
+
const blob = new Blob([data], { type: "octet/stream" });
|
|
9325
|
+
let extension = undefined;
|
|
9326
|
+
if (file.name.toLowerCase().indexOf(".dds") > 0) {
|
|
9327
|
+
extension = ".dds";
|
|
9328
|
+
}
|
|
9329
|
+
else if (file.name.toLowerCase().indexOf(".env") > 0) {
|
|
9330
|
+
extension = ".env";
|
|
9331
|
+
}
|
|
9332
|
+
const reader = new FileReader();
|
|
9333
|
+
reader.readAsDataURL(blob);
|
|
9334
|
+
reader.onloadend = () => {
|
|
9335
|
+
const base64data = reader.result;
|
|
9336
|
+
if (extension === ".dds" || extension === ".env") {
|
|
9337
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
9338
|
+
this._originalTexture.updateURL(base64data, extension, async () => await this.grabOriginalTexture());
|
|
9339
|
+
}
|
|
9340
|
+
else {
|
|
9341
|
+
const texture = new Texture(base64data, this._scene, this._originalTexture.noMipmap, false, Texture.NEAREST_SAMPLINGMODE, () => {
|
|
9342
|
+
// eslint-disable-next-line github/no-then
|
|
9343
|
+
ApplyChannelsToTextureDataAsync(texture, texture.getSize().width, texture.getSize().height, 0, { R: true, G: true, B: true, A: true }).then(async (pixels) => {
|
|
9344
|
+
if (this._tool && this._tool.reset) {
|
|
9345
|
+
this._tool.reset();
|
|
9346
|
+
}
|
|
9347
|
+
texture.dispose();
|
|
9348
|
+
this.setSize(texture.getSize());
|
|
9349
|
+
this.paintPixelsOnCanvas(pixels, this._2DCanvas);
|
|
9350
|
+
await this.updateTexture();
|
|
9351
|
+
this._setMipLevel(0);
|
|
9352
|
+
});
|
|
9353
|
+
});
|
|
9354
|
+
}
|
|
9355
|
+
};
|
|
9356
|
+
}, undefined, true);
|
|
9357
|
+
}
|
|
9358
|
+
saveTexture() {
|
|
9359
|
+
const canvas = this._editing3D ? this._3DCanvas : this._2DCanvas;
|
|
9360
|
+
Tools.ToBlob(canvas, (blob) => {
|
|
9361
|
+
Tools.Download(blob, this._originalTexture.name);
|
|
9362
|
+
});
|
|
9363
|
+
}
|
|
9364
|
+
toolInteractionEnabled() {
|
|
9365
|
+
return !(this._keyMap.get(this.PAN_KEY) || this._isPanning);
|
|
9366
|
+
}
|
|
9367
|
+
dispose() {
|
|
9368
|
+
if (this._didEdit) {
|
|
9369
|
+
this._originalTextureProperties._texture?.dispose();
|
|
9370
|
+
}
|
|
9371
|
+
if (this._tool) {
|
|
9372
|
+
this._tool.deactivate();
|
|
9373
|
+
}
|
|
9374
|
+
this._paintCanvas.parentNode?.removeChild(this._paintCanvas);
|
|
9375
|
+
this._3DPlane.dispose();
|
|
9376
|
+
this._3DCanvasTexture.dispose();
|
|
9377
|
+
this._3DScene.dispose();
|
|
9378
|
+
this._3DEngine.dispose();
|
|
9379
|
+
this._plane?.dispose();
|
|
9380
|
+
this._channelsTexture.dispose();
|
|
9381
|
+
this._planeMaterial.dispose();
|
|
9382
|
+
this._camera.dispose();
|
|
9383
|
+
this._scene.dispose();
|
|
9384
|
+
this._engine.dispose();
|
|
9385
|
+
}
|
|
9386
|
+
}
|
|
9387
|
+
|
|
9388
|
+
const useStyles$8 = makeStyles({
|
|
9389
|
+
channelsBar: {
|
|
9390
|
+
display: "flex",
|
|
9391
|
+
flexDirection: "column",
|
|
9392
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
9393
|
+
padding: tokens.spacingVerticalXS,
|
|
9394
|
+
gap: tokens.spacingVerticalXS,
|
|
9395
|
+
borderRadius: tokens.borderRadiusMedium,
|
|
9396
|
+
boxShadow: tokens.shadow8,
|
|
9397
|
+
},
|
|
9398
|
+
channel: {
|
|
9399
|
+
display: "flex",
|
|
9400
|
+
alignItems: "center",
|
|
9401
|
+
gap: tokens.spacingHorizontalXS,
|
|
9402
|
+
padding: tokens.spacingVerticalXS,
|
|
9403
|
+
borderRadius: tokens.borderRadiusMedium,
|
|
9404
|
+
},
|
|
9405
|
+
channelLabel: {
|
|
9406
|
+
fontWeight: tokens.fontWeightSemibold,
|
|
9407
|
+
margin: `0 ${tokens.spacingHorizontalXS}`,
|
|
9408
|
+
textAlign: "center",
|
|
9409
|
+
},
|
|
9410
|
+
channelR: {
|
|
9411
|
+
color: tokens.colorPaletteRedBorderActive,
|
|
9412
|
+
},
|
|
9413
|
+
channelG: {
|
|
9414
|
+
color: tokens.colorPaletteGreenBorderActive,
|
|
9415
|
+
},
|
|
9416
|
+
channelB: {
|
|
9417
|
+
color: tokens.colorPaletteBlueBorderActive,
|
|
9418
|
+
},
|
|
9419
|
+
channelA: {
|
|
9420
|
+
color: tokens.colorNeutralForeground1,
|
|
9421
|
+
},
|
|
9422
|
+
uneditable: {
|
|
9423
|
+
opacity: 0.5,
|
|
9424
|
+
},
|
|
9425
|
+
});
|
|
9426
|
+
/**
|
|
9427
|
+
* Displays channel visibility and editability controls
|
|
9428
|
+
* @param props - The channels bar properties
|
|
9429
|
+
* @returns The channels bar component
|
|
9430
|
+
*/
|
|
9431
|
+
const ChannelsBar = (props) => {
|
|
9432
|
+
const { channels, setChannels } = props;
|
|
9433
|
+
const classes = useStyles$8();
|
|
9434
|
+
const toggleVisibility = useCallback((index) => {
|
|
9435
|
+
const newChannels = [...channels];
|
|
9436
|
+
newChannels[index] = { ...newChannels[index], visible: !newChannels[index].visible };
|
|
9437
|
+
setChannels(newChannels);
|
|
9438
|
+
}, [channels, setChannels]);
|
|
9439
|
+
const toggleEditable = useCallback((index) => {
|
|
9440
|
+
const newChannels = [...channels];
|
|
9441
|
+
newChannels[index] = { ...newChannels[index], editable: !newChannels[index].editable };
|
|
9442
|
+
setChannels(newChannels);
|
|
9443
|
+
}, [channels, setChannels]);
|
|
9444
|
+
const getChannelColorClass = (id) => {
|
|
9445
|
+
switch (id) {
|
|
9446
|
+
case "R":
|
|
9447
|
+
return classes.channelR;
|
|
9448
|
+
case "G":
|
|
9449
|
+
return classes.channelG;
|
|
9450
|
+
case "B":
|
|
9451
|
+
return classes.channelB;
|
|
9452
|
+
default:
|
|
9453
|
+
return classes.channelA;
|
|
9454
|
+
}
|
|
9455
|
+
};
|
|
9456
|
+
return (jsx("div", { className: classes.channelsBar, children: channels.map((channel, index) => {
|
|
9457
|
+
const visTip = channel.visible ? "Hide" : "Show";
|
|
9458
|
+
const editTip = channel.editable ? "Lock" : "Unlock";
|
|
9459
|
+
return (jsxs("div", { className: `${classes.channel} ${!channel.editable ? classes.uneditable : ""}`, children: [jsx(Tooltip, { content: `${visTip} ${channel.name}`, relationship: "label", positioning: "before", children: jsx(ToggleButton$1, { appearance: "transparent", size: "small", checked: channel.visible, icon: channel.visible ? jsx(EyeRegular, {}) : jsx(EyeOffRegular, {}), onClick: () => toggleVisibility(index) }) }), jsx(Tooltip, { content: `${editTip} ${channel.name}`, relationship: "label", positioning: "before", children: jsx(ToggleButton$1, { appearance: "transparent", size: "small", checked: channel.editable, icon: channel.editable ? jsx(LockOpenRegular, {}) : jsx(LockClosedRegular, {}), onClick: () => toggleEditable(index) }) }), jsx(Body1, { className: mergeClasses(classes.channelLabel, getChannelColorClass(channel.id)), children: channel.id })] }, channel.id));
|
|
9460
|
+
}) }));
|
|
9461
|
+
};
|
|
9462
|
+
|
|
9463
|
+
const useStyles$7 = makeStyles({
|
|
9464
|
+
propertiesBar: {
|
|
9465
|
+
display: "flex",
|
|
9466
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
9467
|
+
alignItems: "center",
|
|
9468
|
+
padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalS}`,
|
|
9469
|
+
gap: tokens.spacingHorizontalS,
|
|
9470
|
+
borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
9471
|
+
flexWrap: "wrap",
|
|
9472
|
+
},
|
|
9473
|
+
section: {
|
|
9474
|
+
display: "flex",
|
|
9475
|
+
alignItems: "center",
|
|
9476
|
+
gap: tokens.spacingHorizontalXS,
|
|
9477
|
+
},
|
|
9478
|
+
dimensionsForm: {
|
|
9479
|
+
display: "flex",
|
|
9480
|
+
alignItems: "center",
|
|
9481
|
+
gap: tokens.spacingHorizontalXS,
|
|
9482
|
+
},
|
|
9483
|
+
dimensionInput: {
|
|
9484
|
+
width: "60px",
|
|
9485
|
+
},
|
|
9486
|
+
pixelData: {
|
|
9487
|
+
display: "flex",
|
|
9488
|
+
alignItems: "center",
|
|
9489
|
+
gap: tokens.spacingHorizontalXS,
|
|
9490
|
+
fontSize: tokens.fontSizeBase200,
|
|
9491
|
+
fontFamily: tokens.fontFamilyMonospace,
|
|
9492
|
+
},
|
|
9493
|
+
pixelDataLabel: {
|
|
9494
|
+
color: tokens.colorNeutralForeground3,
|
|
9495
|
+
},
|
|
9496
|
+
pixelDataValue: {
|
|
9497
|
+
color: tokens.colorNeutralForeground1,
|
|
9498
|
+
minWidth: "32px",
|
|
9499
|
+
},
|
|
9500
|
+
faceButton: {
|
|
9501
|
+
minWidth: "auto",
|
|
9502
|
+
paddingLeft: tokens.spacingHorizontalS,
|
|
9503
|
+
paddingRight: tokens.spacingHorizontalS,
|
|
9504
|
+
},
|
|
9505
|
+
spacer: {
|
|
9506
|
+
flex: 1,
|
|
9507
|
+
},
|
|
9508
|
+
uploadInput: {
|
|
9509
|
+
display: "none",
|
|
9510
|
+
},
|
|
9511
|
+
});
|
|
9512
|
+
const PixelDataDisplay = ({ label, value }) => {
|
|
9513
|
+
const classes = useStyles$7();
|
|
9514
|
+
return (jsxs("span", { className: classes.pixelData, children: [jsxs(Label, { className: classes.pixelDataLabel, children: [label, ":"] }), jsx(Label, { className: classes.pixelDataValue, children: value !== undefined ? value : "-" })] }));
|
|
9515
|
+
};
|
|
9516
|
+
const CubeFaces = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
|
|
9517
|
+
/**
|
|
9518
|
+
* Properties bar component showing texture info and actions
|
|
9519
|
+
* @param props - The properties bar properties
|
|
9520
|
+
* @returns The properties bar component
|
|
9521
|
+
*/
|
|
9522
|
+
const PropertiesBar = (props) => {
|
|
9523
|
+
const { texture, size, saveTexture, pixelData, face, setFace, resetTexture, resizeTexture, uploadTexture, mipLevel, setMipLevel } = props;
|
|
9524
|
+
const classes = useStyles$7();
|
|
9525
|
+
const uploadInputRef = useRef(null);
|
|
9526
|
+
const [width, setWidth] = useState(size.width);
|
|
9527
|
+
const [height, setHeight] = useState(size.height);
|
|
9528
|
+
// Update local state when size prop changes
|
|
9529
|
+
useEffect(() => {
|
|
9530
|
+
setWidth(size.width);
|
|
9531
|
+
setHeight(size.height);
|
|
9532
|
+
}, [size.width, size.height]);
|
|
9533
|
+
const maxLevels = Math.floor(Math.log2(Math.max(texture.getSize().width, texture.getSize().height)));
|
|
9534
|
+
const engine = texture.getScene()?.getEngine();
|
|
9535
|
+
const mipsEnabled = !texture.noMipmap && engine?.getCaps().textureLOD;
|
|
9536
|
+
const handleUploadClick = useCallback(() => {
|
|
9537
|
+
uploadInputRef.current?.click();
|
|
9538
|
+
}, []);
|
|
9539
|
+
const handleFileChange = useCallback((evt) => {
|
|
9540
|
+
const files = evt.target.files;
|
|
9541
|
+
if (files && files.length) {
|
|
9542
|
+
uploadTexture(files[0]);
|
|
9543
|
+
}
|
|
9544
|
+
evt.target.value = "";
|
|
9545
|
+
}, [uploadTexture]);
|
|
9546
|
+
const handleResize = useCallback(() => {
|
|
9547
|
+
resizeTexture(width, height);
|
|
9548
|
+
}, [width, height, resizeTexture]);
|
|
9549
|
+
const getNewDimension = (oldDim, newDim) => {
|
|
9550
|
+
const parsed = parseInt(newDim);
|
|
9551
|
+
if (!isNaN(parsed) && parsed > 0 && Number.isInteger(parsed)) {
|
|
9552
|
+
return parsed;
|
|
9553
|
+
}
|
|
9554
|
+
return oldDim;
|
|
9555
|
+
};
|
|
9556
|
+
return (jsxs("div", { className: classes.propertiesBar, children: [jsxs("div", { className: classes.section, children: [jsx(Label, { children: "W:" }), jsx(Input, { className: classes.dimensionInput, size: "small", type: "text", value: width.toString(), readOnly: texture.isCube, onChange: (_, data) => setWidth(getNewDimension(width, data.value)) }), jsx(Label, { children: "H:" }), jsx(Input, { className: classes.dimensionInput, size: "small", type: "text", value: height.toString(), readOnly: texture.isCube, onChange: (_, data) => setHeight(getNewDimension(height, data.value)) }), !texture.isCube && (jsx(Tooltip, { content: "Resize", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ResizeRegular, {}), onClick: handleResize }) }))] }), jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(PixelDataDisplay, { label: "X", value: pixelData.x }), jsx(PixelDataDisplay, { label: "Y", value: pixelData.y })] }), jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(PixelDataDisplay, { label: "R", value: pixelData.r }), jsx(PixelDataDisplay, { label: "G", value: pixelData.g }), jsx(PixelDataDisplay, { label: "B", value: pixelData.b }), jsx(PixelDataDisplay, { label: "A", value: pixelData.a })] }), texture.isCube && (jsxs(Fragment, { children: [jsx(ToolbarDivider, {}), jsx(Toolbar$1, { size: "small", children: CubeFaces.map((label, index) => (jsx(ToolbarButton, { className: classes.faceButton, appearance: face === index ? "primary" : "subtle", onClick: () => setFace(index), children: label }, label))) })] })), mipsEnabled && (jsxs(Fragment, { children: [jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(Label, { children: "MIP:" }), jsx(Tooltip, { content: "Mip Preview Up", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ChevronUpRegular, {}), disabled: mipLevel <= 0, onClick: () => setMipLevel(mipLevel - 1) }) }), jsx(Label, { children: mipLevel }), jsx(Tooltip, { content: "Mip Preview Down", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ChevronDownRegular, {}), disabled: mipLevel >= maxLevels, onClick: () => setMipLevel(mipLevel + 1) }) })] })] })), jsx("div", { className: classes.spacer }), jsxs(Toolbar$1, { size: "small", children: [jsx(Tooltip, { content: "Reset", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ArrowResetRegular, {}), onClick: resetTexture }) }), jsx(Tooltip, { content: "Upload", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ArrowUploadRegular, {}), onClick: handleUploadClick }) }), jsx("input", { ref: uploadInputRef, className: classes.uploadInput, type: "file", accept: ".jpg, .png, .tga, .dds, .env, .exr", onChange: handleFileChange }), jsx(Tooltip, { content: "Save", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(SaveRegular, {}), onClick: saveTexture }) })] })] }));
|
|
9557
|
+
};
|
|
9558
|
+
|
|
9559
|
+
const useStyles$6 = makeStyles({
|
|
9560
|
+
statusBar: {
|
|
9561
|
+
display: "flex",
|
|
9562
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
9563
|
+
alignItems: "center",
|
|
9564
|
+
justifyContent: "space-between",
|
|
9565
|
+
padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalM}`,
|
|
9566
|
+
fontSize: tokens.fontSizeBase200,
|
|
9567
|
+
color: tokens.colorNeutralForeground2,
|
|
9568
|
+
borderTop: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
9569
|
+
minHeight: "24px",
|
|
9570
|
+
},
|
|
9571
|
+
fileName: {
|
|
9572
|
+
overflow: "hidden",
|
|
9573
|
+
textOverflow: "ellipsis",
|
|
9574
|
+
whiteSpace: "nowrap",
|
|
9575
|
+
},
|
|
9576
|
+
mipInfo: {
|
|
9577
|
+
flexShrink: 0,
|
|
9578
|
+
},
|
|
9579
|
+
});
|
|
9580
|
+
/**
|
|
9581
|
+
* Displays status information about the texture
|
|
9582
|
+
* @param props - The status bar properties
|
|
9583
|
+
* @returns The status bar component
|
|
9584
|
+
*/
|
|
9585
|
+
const StatusBar = (props) => {
|
|
9586
|
+
const { texture, mipLevel } = props;
|
|
9587
|
+
const classes = useStyles$6();
|
|
9588
|
+
const factor = Math.pow(2, mipLevel);
|
|
9589
|
+
const width = Math.ceil(texture.getSize().width / factor);
|
|
9590
|
+
const height = Math.ceil(texture.getSize().height / factor);
|
|
9591
|
+
return (jsxs("div", { className: classes.statusBar, children: [jsx("span", { className: classes.fileName, children: texture.name }), !texture.noMipmap && (jsxs("span", { className: classes.mipInfo, children: ["MIP Preview: ", mipLevel, " (", width, "\u00D7", height, ")"] }))] }));
|
|
9592
|
+
};
|
|
9593
|
+
|
|
9594
|
+
const useStyles$5 = makeStyles({
|
|
9595
|
+
toolbar: {
|
|
9596
|
+
display: "flex",
|
|
9597
|
+
flexDirection: "column",
|
|
9598
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
9599
|
+
padding: tokens.spacingVerticalXS,
|
|
9600
|
+
gap: tokens.spacingVerticalXS,
|
|
9601
|
+
borderRadius: tokens.borderRadiusMedium,
|
|
9602
|
+
boxShadow: tokens.shadow8,
|
|
9603
|
+
},
|
|
9604
|
+
toolsSection: {
|
|
9605
|
+
display: "flex",
|
|
9606
|
+
flexDirection: "column",
|
|
9607
|
+
gap: tokens.spacingVerticalXXS,
|
|
9608
|
+
},
|
|
9609
|
+
toolButton: {
|
|
9610
|
+
minWidth: "36px",
|
|
9611
|
+
minHeight: "36px",
|
|
9612
|
+
padding: tokens.spacingVerticalXS,
|
|
9613
|
+
},
|
|
9614
|
+
toolIcon: {
|
|
9615
|
+
width: "24px",
|
|
9616
|
+
height: "24px",
|
|
9617
|
+
},
|
|
9618
|
+
colorSection: {
|
|
9619
|
+
display: "flex",
|
|
9620
|
+
justifyContent: "center",
|
|
9621
|
+
margin: tokens.spacingVerticalS,
|
|
9622
|
+
},
|
|
9623
|
+
});
|
|
9624
|
+
/**
|
|
9625
|
+
* Toolbar component for texture editing tools
|
|
9626
|
+
* @param props - The toolbar properties
|
|
9627
|
+
* @returns The toolbar component
|
|
9628
|
+
*/
|
|
9629
|
+
const ToolBar = (props) => {
|
|
9630
|
+
const { tools, changeTool, activeToolIndex, metadata, setMetadata, hasAlpha } = props;
|
|
9631
|
+
const classes = useStyles$5();
|
|
9632
|
+
const computeRGBAColor = useCallback(() => {
|
|
9633
|
+
const opacityInt = Math.floor(metadata.alpha * 255);
|
|
9634
|
+
const opacityHex = opacityInt.toString(16).padStart(2, "0");
|
|
9635
|
+
return Color4.FromHexString(`${metadata.color}${opacityHex}`);
|
|
9636
|
+
}, [metadata.color, metadata.alpha]);
|
|
9637
|
+
const handleColorChange = useCallback((color) => {
|
|
9638
|
+
const newMetadata = {
|
|
9639
|
+
color: color.toHexString(true),
|
|
9640
|
+
alpha: color.a ?? 1,
|
|
9641
|
+
};
|
|
9642
|
+
if (newMetadata.color !== metadata.color || newMetadata.alpha !== metadata.alpha) {
|
|
9643
|
+
setMetadata(newMetadata);
|
|
9644
|
+
}
|
|
9645
|
+
}, [metadata, setMetadata]);
|
|
9646
|
+
const handleToolClick = useCallback((index) => {
|
|
9647
|
+
if (activeToolIndex === index) {
|
|
9648
|
+
// Deselect current tool
|
|
9649
|
+
changeTool(-1);
|
|
9650
|
+
}
|
|
9651
|
+
else {
|
|
9652
|
+
changeTool(index);
|
|
9653
|
+
}
|
|
9654
|
+
}, [activeToolIndex, changeTool]);
|
|
9655
|
+
return (jsxs("div", { className: classes.toolbar, children: [jsx("div", { className: classes.colorSection, children: jsx(Tooltip, { relationship: "label", content: "Pick Tool Color", positioning: "after", children: jsx(ColorPickerPopup, { value: hasAlpha ? computeRGBAColor() : Color3.FromHexString(metadata.color), onChange: handleColorChange }) }) }), jsx(Divider, {}), jsx("div", { className: classes.toolsSection, children: tools.map((tool, index) => {
|
|
9656
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
9657
|
+
const IconComponent = tool.icon;
|
|
9658
|
+
return (jsx(Tooltip, { content: tool.name, relationship: "label", positioning: "after", children: jsx(ToggleButton$1, { className: classes.toolButton, appearance: "subtle", checked: index === activeToolIndex, onClick: () => handleToolClick(index), icon: jsx(IconComponent, {}) }) }, index));
|
|
9659
|
+
}) })] }));
|
|
9660
|
+
};
|
|
9661
|
+
|
|
9662
|
+
const useStyles$4 = makeStyles({
|
|
9663
|
+
textureEditor: {
|
|
9664
|
+
display: "flex",
|
|
9665
|
+
flexDirection: "column",
|
|
9666
|
+
height: "100%",
|
|
9667
|
+
width: "100%",
|
|
9668
|
+
backgroundColor: tokens.colorNeutralBackground3,
|
|
9669
|
+
color: tokens.colorNeutralForeground1,
|
|
9670
|
+
overflow: "hidden",
|
|
9671
|
+
},
|
|
9672
|
+
mainContent: {
|
|
9673
|
+
display: "flex",
|
|
9674
|
+
flex: 1,
|
|
9675
|
+
overflow: "hidden",
|
|
9676
|
+
position: "relative",
|
|
9677
|
+
},
|
|
9678
|
+
canvasContainer: {
|
|
9679
|
+
flex: 1,
|
|
9680
|
+
position: "relative",
|
|
9681
|
+
overflow: "hidden",
|
|
9682
|
+
},
|
|
9683
|
+
canvasUI: {
|
|
9684
|
+
width: "100%",
|
|
9685
|
+
height: "100%",
|
|
9686
|
+
outline: "none",
|
|
9687
|
+
},
|
|
9688
|
+
canvas2D: {
|
|
9689
|
+
display: "none",
|
|
9690
|
+
},
|
|
9691
|
+
canvas3D: {
|
|
9692
|
+
display: "none",
|
|
9693
|
+
},
|
|
9694
|
+
sidebarLeft: {
|
|
9695
|
+
display: "flex",
|
|
9696
|
+
flexDirection: "column",
|
|
9697
|
+
position: "absolute",
|
|
9698
|
+
left: tokens.spacingHorizontalM,
|
|
9699
|
+
top: tokens.spacingVerticalM,
|
|
9700
|
+
},
|
|
9701
|
+
sidebarRight: {
|
|
9702
|
+
display: "flex",
|
|
9703
|
+
flexDirection: "column",
|
|
9704
|
+
position: "absolute",
|
|
9705
|
+
right: tokens.spacingHorizontalM,
|
|
9706
|
+
top: tokens.spacingVerticalM,
|
|
9707
|
+
},
|
|
9708
|
+
toolSettingsContainer: {
|
|
9709
|
+
position: "absolute",
|
|
9710
|
+
left: tokens.spacingHorizontalM,
|
|
9711
|
+
bottom: tokens.spacingVerticalM,
|
|
9712
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
9713
|
+
borderRadius: tokens.borderRadiusMedium,
|
|
9714
|
+
padding: tokens.spacingVerticalS,
|
|
9715
|
+
boxShadow: tokens.shadow8,
|
|
9716
|
+
},
|
|
9717
|
+
});
|
|
9718
|
+
const PREVIEW_UPDATE_DELAY_MS = 160;
|
|
9719
|
+
/**
|
|
9720
|
+
* Main texture editor component
|
|
9721
|
+
* @param props - The texture editor properties
|
|
9722
|
+
* @returns The texture editor component
|
|
9723
|
+
*/
|
|
9724
|
+
const TextureEditor = (props) => {
|
|
9725
|
+
const { texture, toolProviders = [], window: editorWindow, onUpdate } = props;
|
|
9726
|
+
const classes = useStyles$4();
|
|
9727
|
+
// Canvas refs
|
|
9728
|
+
const uiCanvasRef = useRef(null);
|
|
9729
|
+
const canvas2DRef = useRef(null);
|
|
9730
|
+
const canvas3DRef = useRef(null);
|
|
9731
|
+
const timerRef = useRef(null);
|
|
9732
|
+
const canvasManagerRef = useRef(null);
|
|
9733
|
+
// State
|
|
9734
|
+
const [activeToolIndex, setActiveToolIndex] = useState(-1);
|
|
9735
|
+
const [metadata, setMetadataState] = useState({
|
|
9736
|
+
color: "#ffffff",
|
|
9737
|
+
alpha: 1,
|
|
9738
|
+
select: {
|
|
9739
|
+
x1: -1,
|
|
9740
|
+
y1: -1,
|
|
9741
|
+
x2: -1,
|
|
9742
|
+
y2: -1,
|
|
9743
|
+
},
|
|
9744
|
+
});
|
|
9745
|
+
const [channels, setChannels] = useState(() => {
|
|
9746
|
+
const baseChannels = [
|
|
9747
|
+
{ name: "Red", visible: true, editable: true, id: "R" },
|
|
9748
|
+
{ name: "Green", visible: true, editable: true, id: "G" },
|
|
9749
|
+
{ name: "Blue", visible: true, editable: true, id: "B" },
|
|
9750
|
+
];
|
|
9751
|
+
baseChannels.push({
|
|
9752
|
+
name: texture.isCube ? "Display" : "Alpha",
|
|
9753
|
+
visible: true,
|
|
9754
|
+
editable: true,
|
|
9755
|
+
id: "A",
|
|
9756
|
+
});
|
|
9757
|
+
return baseChannels;
|
|
9758
|
+
});
|
|
9759
|
+
const [pixelData, setPixelData] = useState({});
|
|
9760
|
+
const [face, setFace] = useState(0);
|
|
9761
|
+
const [mipLevel, setMipLevel] = useState(0);
|
|
9762
|
+
const [size, setSize] = useState(texture.getSize());
|
|
9763
|
+
// Callbacks
|
|
9764
|
+
const textureDidUpdate = useCallback(() => {
|
|
9765
|
+
if (timerRef.current != null) {
|
|
9766
|
+
window.clearTimeout(timerRef.current);
|
|
9767
|
+
}
|
|
9768
|
+
timerRef.current = window.setTimeout(() => {
|
|
9769
|
+
onUpdate?.();
|
|
9770
|
+
timerRef.current = null;
|
|
9771
|
+
}, PREVIEW_UPDATE_DELAY_MS);
|
|
9772
|
+
}, [onUpdate]);
|
|
9773
|
+
const setMetadata = useCallback((newMetadata) => {
|
|
9774
|
+
setMetadataState((prev) => {
|
|
9775
|
+
const data = { ...prev, ...newMetadata };
|
|
9776
|
+
if (canvasManagerRef.current) {
|
|
9777
|
+
canvasManagerRef.current.metadata = data;
|
|
9778
|
+
}
|
|
9779
|
+
return data;
|
|
9780
|
+
});
|
|
9781
|
+
}, []);
|
|
9782
|
+
const getToolParameters = () => {
|
|
9783
|
+
const manager = canvasManagerRef.current;
|
|
9784
|
+
return {
|
|
9785
|
+
scene: manager.scene,
|
|
9786
|
+
canvas2D: manager.canvas2D,
|
|
9787
|
+
scene3D: manager.scene3D,
|
|
9788
|
+
size: manager.size,
|
|
9789
|
+
updateTexture: () => void manager.updateTexture(),
|
|
9790
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
9791
|
+
startPainting: () => manager.startPainting(),
|
|
9792
|
+
stopPainting: () => manager.stopPainting(),
|
|
9793
|
+
updatePainting: () => manager.updatePainting(),
|
|
9794
|
+
metadata,
|
|
9795
|
+
setMetadata,
|
|
9796
|
+
getMouseCoordinates: (pointerInfo) => manager.getMouseCoordinates(pointerInfo),
|
|
9797
|
+
interactionEnabled: () => manager.toolInteractionEnabled(),
|
|
9798
|
+
};
|
|
9799
|
+
};
|
|
9800
|
+
const getToolParametersRef = useRef(getToolParameters);
|
|
9801
|
+
getToolParametersRef.current = getToolParameters;
|
|
9802
|
+
const tools = useMemo(() => toolProviders?.map((provider) => provider.getTool({ getParameters: () => getToolParametersRef.current() })), [toolProviders]);
|
|
9803
|
+
const changeTool = useCallback((index) => {
|
|
9804
|
+
if (canvasManagerRef.current) {
|
|
9805
|
+
if (index !== -1 && tools[index]) {
|
|
9806
|
+
canvasManagerRef.current.tool = {
|
|
9807
|
+
is3D: toolProviders[index].is3D ?? false,
|
|
9808
|
+
activate: () => tools[index].activate(),
|
|
9809
|
+
deactivate: () => tools[index].deactivate(),
|
|
9810
|
+
reset: () => tools[index].reset?.(),
|
|
9811
|
+
};
|
|
9812
|
+
}
|
|
9813
|
+
else {
|
|
9814
|
+
canvasManagerRef.current.tool = null;
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
setActiveToolIndex(index);
|
|
9818
|
+
}, [toolProviders, tools]);
|
|
9819
|
+
const saveTexture = useCallback(() => {
|
|
9820
|
+
canvasManagerRef.current?.saveTexture();
|
|
9821
|
+
}, []);
|
|
9822
|
+
const resetTexture = useCallback(() => {
|
|
9823
|
+
canvasManagerRef.current?.reset();
|
|
9824
|
+
}, []);
|
|
9825
|
+
const resizeTexture = useCallback((width, height) => {
|
|
9826
|
+
void canvasManagerRef.current?.resize({ width, height });
|
|
9827
|
+
}, []);
|
|
9828
|
+
const uploadTexture = useCallback((file) => {
|
|
9829
|
+
canvasManagerRef.current?.upload(file);
|
|
9830
|
+
}, []);
|
|
9831
|
+
// Initialize canvas manager
|
|
9832
|
+
useEffect(() => {
|
|
9833
|
+
if (!uiCanvasRef.current || !canvas2DRef.current || !canvas3DRef.current) {
|
|
9834
|
+
return;
|
|
9835
|
+
}
|
|
9836
|
+
const manager = new TextureCanvasManager(texture, editorWindow ?? uiCanvasRef.current.ownerDocument.defaultView ?? window, uiCanvasRef.current, canvas2DRef.current, canvas3DRef.current, setPixelData, metadata, textureDidUpdate, setMetadata, setMipLevel);
|
|
9837
|
+
canvasManagerRef.current = manager;
|
|
9838
|
+
setSize(manager.size);
|
|
9839
|
+
return () => {
|
|
9840
|
+
manager.dispose();
|
|
9841
|
+
canvasManagerRef.current = null;
|
|
9842
|
+
};
|
|
9843
|
+
}, [texture, editorWindow]);
|
|
9844
|
+
// Update canvas manager when channels/face/mipLevel change
|
|
9845
|
+
useEffect(() => {
|
|
9846
|
+
if (canvasManagerRef.current) {
|
|
9847
|
+
canvasManagerRef.current.channels = [...channels];
|
|
9848
|
+
}
|
|
9849
|
+
}, [channels]);
|
|
9850
|
+
useEffect(() => {
|
|
9851
|
+
if (canvasManagerRef.current) {
|
|
9852
|
+
canvasManagerRef.current.face = face;
|
|
9853
|
+
}
|
|
9854
|
+
}, [face]);
|
|
9855
|
+
useEffect(() => {
|
|
9856
|
+
if (canvasManagerRef.current) {
|
|
9857
|
+
canvasManagerRef.current.mipLevel = mipLevel;
|
|
9858
|
+
}
|
|
9859
|
+
}, [mipLevel]);
|
|
9860
|
+
// Compute cursor style
|
|
9861
|
+
let cursor = "default";
|
|
9862
|
+
if (canvasManagerRef.current && !canvasManagerRef.current.toolInteractionEnabled()) {
|
|
9863
|
+
cursor = "grab";
|
|
9864
|
+
}
|
|
9865
|
+
else if (toolProviders[activeToolIndex]?.cursor) {
|
|
9866
|
+
cursor = toolProviders[activeToolIndex].cursor;
|
|
9867
|
+
}
|
|
9868
|
+
const hasAlpha = texture.textureFormat === -1 || texture.textureFormat === Constants.TEXTUREFORMAT_RGBA;
|
|
9869
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
9870
|
+
const CurrentToolSettings = useMemo(() => tools[activeToolIndex]?.settingsComponent, [tools, activeToolIndex]);
|
|
9871
|
+
return (jsxs("div", { className: classes.textureEditor, children: [jsx(PropertiesBar, { texture: texture, saveTexture: saveTexture, pixelData: pixelData, face: face, setFace: setFace, resetTexture: resetTexture, resizeTexture: resizeTexture, uploadTexture: uploadTexture, mipLevel: mipLevel, setMipLevel: setMipLevel, size: canvasManagerRef.current?.size || size }), jsxs("div", { className: classes.mainContent, children: [jsxs("div", { className: classes.canvasContainer, style: { cursor }, children: [jsx("canvas", { ref: uiCanvasRef, className: classes.canvasUI, tabIndex: 1 }), jsx("canvas", { ref: canvas2DRef, className: classes.canvas2D }), jsx("canvas", { ref: canvas3DRef, className: classes.canvas3D })] }), CurrentToolSettings && (jsx("div", { className: classes.toolSettingsContainer, children: jsx(CurrentToolSettings, {}) })), !texture.isCube && (jsx("div", { className: classes.sidebarLeft, children: jsx(ToolBar, { tools: toolProviders, activeToolIndex: activeToolIndex, changeTool: changeTool, metadata: metadata, setMetadata: setMetadata, hasAlpha: hasAlpha }) })), jsx("div", { className: classes.sidebarRight, children: jsx(ChannelsBar, { channels: channels, setChannels: setChannels }) })] }), jsx(StatusBar, { texture: texture, mipLevel: mipLevel })] }));
|
|
9872
|
+
};
|
|
9873
|
+
|
|
9874
|
+
const useStyles$3 = makeStyles({
|
|
9875
|
+
settingsContainer: {
|
|
9876
|
+
display: "flex",
|
|
9877
|
+
flexDirection: "column",
|
|
9878
|
+
gap: tokens.spacingVerticalS,
|
|
9879
|
+
minWidth: "150px",
|
|
9880
|
+
},
|
|
9881
|
+
sliderRow: {
|
|
9882
|
+
display: "flex",
|
|
9883
|
+
flexDirection: "column",
|
|
9884
|
+
gap: tokens.spacingVerticalXS,
|
|
9885
|
+
},
|
|
9886
|
+
icon: {
|
|
9887
|
+
rotate: "-90deg",
|
|
9888
|
+
},
|
|
9889
|
+
});
|
|
9890
|
+
const Contrast = {
|
|
9891
|
+
name: "Contrast/Exposure",
|
|
9892
|
+
order: 500,
|
|
9893
|
+
icon: () => {
|
|
9894
|
+
const classes = useStyles$3();
|
|
9895
|
+
return jsx(CircleHalfFillRegular, { className: classes.icon });
|
|
9896
|
+
},
|
|
9897
|
+
is3D: true,
|
|
9898
|
+
getTool: (context) => {
|
|
9899
|
+
let _contrast = 0;
|
|
9900
|
+
let _exposure = 0;
|
|
9901
|
+
const stateChangedObservable = new Observable();
|
|
9902
|
+
/**
|
|
9903
|
+
* Maps slider values to post processing values using an exponential regression
|
|
9904
|
+
* @param sliderValue - The slider value
|
|
9905
|
+
* @returns exposure value
|
|
9906
|
+
*/
|
|
9907
|
+
function computeExposure(sliderValue) {
|
|
9908
|
+
if (sliderValue <= 0) {
|
|
9909
|
+
return 1 - -sliderValue / 100;
|
|
9910
|
+
}
|
|
9911
|
+
else {
|
|
9912
|
+
return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
/**
|
|
9916
|
+
* Maps slider values to post processing values using an exponential regression
|
|
9917
|
+
* @param sliderValue - The slider value
|
|
9918
|
+
* @returns contrast value
|
|
9919
|
+
*/
|
|
9920
|
+
function computeContrast(sliderValue) {
|
|
9921
|
+
if (sliderValue <= 0) {
|
|
9922
|
+
return 1 - -sliderValue / 100;
|
|
9923
|
+
}
|
|
9924
|
+
else {
|
|
9925
|
+
return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
function setExposure(exposure) {
|
|
9929
|
+
_exposure = exposure;
|
|
9930
|
+
stateChangedObservable.notifyObservers();
|
|
9931
|
+
const { scene3D, updateTexture } = context.getParameters();
|
|
9932
|
+
scene3D.imageProcessingConfiguration.isEnabled = true;
|
|
9933
|
+
scene3D.imageProcessingConfiguration.exposure = computeExposure(_exposure);
|
|
9934
|
+
updateTexture();
|
|
9935
|
+
}
|
|
9936
|
+
function setContrast(contrast) {
|
|
9937
|
+
_contrast = contrast;
|
|
9938
|
+
stateChangedObservable.notifyObservers();
|
|
9939
|
+
const { scene3D, updateTexture } = context.getParameters();
|
|
9940
|
+
scene3D.imageProcessingConfiguration.isEnabled = true;
|
|
9941
|
+
scene3D.imageProcessingConfiguration.contrast = computeContrast(_contrast);
|
|
9942
|
+
updateTexture();
|
|
9943
|
+
}
|
|
9944
|
+
return {
|
|
9945
|
+
activate: () => {
|
|
9946
|
+
_contrast = 0;
|
|
9947
|
+
_exposure = 0;
|
|
9948
|
+
setExposure(_exposure);
|
|
9949
|
+
setContrast(_contrast);
|
|
9950
|
+
},
|
|
9951
|
+
deactivate: () => {
|
|
9952
|
+
// No cleanup needed
|
|
9953
|
+
},
|
|
9954
|
+
reset: () => {
|
|
9955
|
+
setExposure(0);
|
|
9956
|
+
setContrast(0);
|
|
9957
|
+
},
|
|
9958
|
+
settingsComponent: () => {
|
|
9959
|
+
const classes = useStyles$3();
|
|
9960
|
+
const [contrast, exposure] = useObservableState(useCallback(() => [_contrast, _exposure], []), stateChangedObservable);
|
|
9961
|
+
const handleContrastChange = (_, data) => {
|
|
9962
|
+
setContrast(data.value);
|
|
9963
|
+
};
|
|
9964
|
+
const handleExposureChange = (_, data) => {
|
|
9965
|
+
setExposure(data.value);
|
|
9966
|
+
};
|
|
9967
|
+
return (jsxs("div", { className: classes.settingsContainer, children: [jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Contrast: ", contrast] }), jsx(Slider, { min: -100, max: 100, value: contrast, onChange: handleContrastChange })] }), jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Exposure: ", exposure] }), jsx(Slider, { min: -100, max: 100, value: exposure, onChange: handleExposureChange })] })] }));
|
|
9968
|
+
},
|
|
9969
|
+
};
|
|
9970
|
+
},
|
|
9971
|
+
};
|
|
9972
|
+
|
|
9973
|
+
/**
|
|
9974
|
+
* Eyedropper tool for picking colors from the texture
|
|
9975
|
+
*/
|
|
9976
|
+
const Eyedropper = {
|
|
9977
|
+
name: "Eyedropper",
|
|
9978
|
+
order: 300,
|
|
9979
|
+
icon: () => jsx(EyedropperRegular, {}),
|
|
9980
|
+
cursor: "crosshair",
|
|
9981
|
+
getTool: (context) => {
|
|
9982
|
+
let pointerObserver = null;
|
|
9983
|
+
let isPicking = false;
|
|
9984
|
+
function pick(pointerInfo) {
|
|
9985
|
+
const { canvas2D, setMetadata, getMouseCoordinates } = context.getParameters();
|
|
9986
|
+
const ctx = canvas2D.getContext("2d");
|
|
9987
|
+
const { x, y } = getMouseCoordinates(pointerInfo);
|
|
9988
|
+
const pixel = ctx.getImageData(x, y, 1, 1).data;
|
|
9989
|
+
setMetadata({
|
|
9990
|
+
color: Color3.FromInts(pixel[0], pixel[1], pixel[2]).toHexString(),
|
|
9991
|
+
alpha: pixel[3] / 255,
|
|
9992
|
+
});
|
|
9993
|
+
}
|
|
9994
|
+
return {
|
|
9995
|
+
activate: () => {
|
|
9996
|
+
pointerObserver = context.getParameters().scene.onPointerObservable.add((pointerInfo) => {
|
|
9997
|
+
if (pointerInfo.pickInfo?.hit) {
|
|
9998
|
+
if (pointerInfo.type === PointerEventTypes.POINTERDOWN && pointerInfo.event.buttons === 1 && context.getParameters().interactionEnabled()) {
|
|
9999
|
+
isPicking = true;
|
|
10000
|
+
pick(pointerInfo);
|
|
10001
|
+
}
|
|
10002
|
+
if (isPicking) {
|
|
10003
|
+
if (pointerInfo.event.buttons !== 1 || !context.getParameters().interactionEnabled()) {
|
|
10004
|
+
isPicking = false;
|
|
10005
|
+
}
|
|
10006
|
+
else {
|
|
10007
|
+
pick(pointerInfo);
|
|
10008
|
+
}
|
|
10009
|
+
}
|
|
10010
|
+
}
|
|
10011
|
+
});
|
|
10012
|
+
isPicking = false;
|
|
10013
|
+
},
|
|
10014
|
+
deactivate: () => {
|
|
10015
|
+
pointerObserver?.remove();
|
|
10016
|
+
},
|
|
10017
|
+
};
|
|
10018
|
+
},
|
|
10019
|
+
};
|
|
10020
|
+
|
|
10021
|
+
/**
|
|
10022
|
+
* Floodfill tool for filling regions with a solid color
|
|
10023
|
+
*/
|
|
10024
|
+
const Floodfill = {
|
|
10025
|
+
name: "Floodfill",
|
|
10026
|
+
order: 400,
|
|
10027
|
+
icon: () => jsx(PaintBucketRegular, {}),
|
|
10028
|
+
cursor: "crosshair",
|
|
10029
|
+
getTool: (context) => {
|
|
10030
|
+
let pointerObserver = null;
|
|
10031
|
+
async function fillAsync() {
|
|
10032
|
+
const { metadata, startPainting, updatePainting, stopPainting } = context.getParameters();
|
|
10033
|
+
const ctx = await startPainting();
|
|
10034
|
+
ctx.fillStyle = metadata.color;
|
|
10035
|
+
ctx.globalAlpha = metadata.alpha;
|
|
10036
|
+
ctx.globalCompositeOperation = "copy";
|
|
10037
|
+
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
10038
|
+
updatePainting();
|
|
10039
|
+
stopPainting();
|
|
10040
|
+
}
|
|
10041
|
+
return {
|
|
10042
|
+
activate: () => {
|
|
10043
|
+
pointerObserver = context.getParameters().scene.onPointerObservable.add((pointerInfo) => {
|
|
10044
|
+
if (pointerInfo.type === PointerEventTypes.POINTERDOWN &&
|
|
10045
|
+
pointerInfo.event.buttons === 1 &&
|
|
10046
|
+
context.getParameters().interactionEnabled() &&
|
|
10047
|
+
pointerInfo.pickInfo?.hit) {
|
|
10048
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
10049
|
+
fillAsync();
|
|
10050
|
+
}
|
|
10051
|
+
});
|
|
10052
|
+
},
|
|
10053
|
+
deactivate: () => {
|
|
10054
|
+
pointerObserver?.remove();
|
|
10055
|
+
},
|
|
10056
|
+
};
|
|
10057
|
+
},
|
|
10058
|
+
};
|
|
10059
|
+
|
|
10060
|
+
const useStyles$2 = makeStyles({
|
|
10061
|
+
settingsContainer: {
|
|
10062
|
+
display: "flex",
|
|
10063
|
+
flexDirection: "column",
|
|
10064
|
+
gap: tokens.spacingVerticalS,
|
|
10065
|
+
minWidth: "150px",
|
|
10066
|
+
},
|
|
10067
|
+
sliderRow: {
|
|
10068
|
+
display: "flex",
|
|
10069
|
+
flexDirection: "column",
|
|
10070
|
+
gap: tokens.spacingVerticalXS,
|
|
10071
|
+
},
|
|
10072
|
+
});
|
|
10073
|
+
const Paintbrush = {
|
|
10074
|
+
name: "Paintbrush",
|
|
10075
|
+
order: 200,
|
|
10076
|
+
icon: () => jsx(InkStrokeRegular, {}),
|
|
10077
|
+
cursor: "crosshair",
|
|
10078
|
+
getTool: (context) => {
|
|
10079
|
+
let pointerObserver = null;
|
|
10080
|
+
let isPainting = false;
|
|
10081
|
+
let _width = 15;
|
|
10082
|
+
let mousePos = null;
|
|
10083
|
+
let ctx = null;
|
|
10084
|
+
let circleCanvas = null;
|
|
10085
|
+
const stateChangedObservable = new Observable();
|
|
10086
|
+
function setWidth(width) {
|
|
10087
|
+
_width = width;
|
|
10088
|
+
stateChangedObservable.notifyObservers();
|
|
10089
|
+
}
|
|
10090
|
+
function paint(pointerInfo) {
|
|
10091
|
+
if (ctx && circleCanvas) {
|
|
10092
|
+
const { getMouseCoordinates, metadata, updatePainting } = context.getParameters();
|
|
10093
|
+
let { x, y } = getMouseCoordinates(pointerInfo);
|
|
10094
|
+
if (metadata.select.x1 !== -1) {
|
|
10095
|
+
x -= metadata.select.x1;
|
|
10096
|
+
y -= metadata.select.y1;
|
|
10097
|
+
}
|
|
10098
|
+
let numSteps, stepVector;
|
|
10099
|
+
stepVector = new Vector2();
|
|
10100
|
+
if (mousePos === null) {
|
|
10101
|
+
mousePos = new Vector2(x, y);
|
|
10102
|
+
numSteps = 1;
|
|
10103
|
+
}
|
|
10104
|
+
else {
|
|
10105
|
+
const maxDistance = _width / 4;
|
|
10106
|
+
const diffVector = new Vector2(x - mousePos.x, y - mousePos.y);
|
|
10107
|
+
numSteps = Math.ceil(diffVector.length() / maxDistance);
|
|
10108
|
+
const trueDistance = diffVector.length() / numSteps;
|
|
10109
|
+
stepVector = diffVector.normalize().multiplyByFloats(trueDistance, trueDistance);
|
|
10110
|
+
}
|
|
10111
|
+
const paintVector = mousePos.clone();
|
|
10112
|
+
for (let stepCount = 0; stepCount < numSteps; stepCount++) {
|
|
10113
|
+
ctx.globalAlpha = 1.0;
|
|
10114
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
10115
|
+
ctx.drawImage(circleCanvas, Math.ceil(paintVector.x - _width / 2), Math.ceil(paintVector.y - _width / 2));
|
|
10116
|
+
ctx.globalAlpha = metadata.alpha;
|
|
10117
|
+
ctx.globalCompositeOperation = "source-over";
|
|
10118
|
+
ctx.drawImage(circleCanvas, Math.ceil(paintVector.x - _width / 2), Math.ceil(paintVector.y - _width / 2));
|
|
10119
|
+
paintVector.addInPlace(stepVector);
|
|
10120
|
+
}
|
|
10121
|
+
updatePainting();
|
|
10122
|
+
mousePos = new Vector2(x, y);
|
|
10123
|
+
}
|
|
10124
|
+
}
|
|
10125
|
+
return {
|
|
10126
|
+
activate: () => {
|
|
10127
|
+
const { scene } = context.getParameters();
|
|
10128
|
+
pointerObserver = scene.onPointerObservable.add(async (pointerInfo) => {
|
|
10129
|
+
const { startPainting, stopPainting, metadata } = context.getParameters();
|
|
10130
|
+
if (!isPainting) {
|
|
10131
|
+
if (pointerInfo.type === PointerEventTypes.POINTERDOWN &&
|
|
10132
|
+
pointerInfo.event.buttons === 1 &&
|
|
10133
|
+
context.getParameters().interactionEnabled() &&
|
|
10134
|
+
pointerInfo.pickInfo?.hit) {
|
|
10135
|
+
isPainting = true;
|
|
10136
|
+
circleCanvas = document.createElement("canvas");
|
|
10137
|
+
circleCanvas.width = _width;
|
|
10138
|
+
circleCanvas.height = _width;
|
|
10139
|
+
const circleCtx = circleCanvas.getContext("2d");
|
|
10140
|
+
circleCtx.imageSmoothingEnabled = false;
|
|
10141
|
+
const pixels = new Array(4 * _width * _width);
|
|
10142
|
+
const dis = (_width * _width) / 4;
|
|
10143
|
+
const rgb = Color3.FromHexString(metadata.color);
|
|
10144
|
+
const r = Math.floor(rgb.r * 255);
|
|
10145
|
+
const g = Math.floor(rgb.g * 255);
|
|
10146
|
+
const b = Math.floor(rgb.b * 255);
|
|
10147
|
+
let idx = 0;
|
|
10148
|
+
const x1 = -Math.floor(_width / 2), x2 = Math.ceil(_width / 2);
|
|
10149
|
+
const y1 = -Math.floor(_width / 2), y2 = Math.ceil(_width / 2);
|
|
10150
|
+
for (let y = y1; y < y2; y++) {
|
|
10151
|
+
for (let x = x1; x < x2; x++) {
|
|
10152
|
+
pixels[idx++] = r;
|
|
10153
|
+
pixels[idx++] = g;
|
|
10154
|
+
pixels[idx++] = b;
|
|
10155
|
+
pixels[idx++] = x * x + y * y <= dis ? 255 : 0;
|
|
10156
|
+
}
|
|
10157
|
+
}
|
|
10158
|
+
circleCtx.putImageData(new ImageData(Uint8ClampedArray.from(pixels), _width, _width), 0, 0);
|
|
10159
|
+
ctx = await startPainting();
|
|
10160
|
+
paint(pointerInfo);
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
else {
|
|
10164
|
+
if (pointerInfo.event.buttons !== 1 || !context.getParameters().interactionEnabled()) {
|
|
10165
|
+
isPainting = false;
|
|
10166
|
+
circleCanvas?.parentNode?.removeChild(circleCanvas);
|
|
10167
|
+
stopPainting();
|
|
10168
|
+
mousePos = null;
|
|
10169
|
+
}
|
|
10170
|
+
else {
|
|
10171
|
+
if (pointerInfo.pickInfo?.hit && pointerInfo.type === PointerEventTypes.POINTERMOVE) {
|
|
10172
|
+
paint(pointerInfo);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
});
|
|
10177
|
+
isPainting = false;
|
|
10178
|
+
},
|
|
10179
|
+
deactivate: () => {
|
|
10180
|
+
isPainting = false;
|
|
10181
|
+
pointerObserver?.remove();
|
|
10182
|
+
},
|
|
10183
|
+
settingsComponent: () => {
|
|
10184
|
+
const classes = useStyles$2();
|
|
10185
|
+
const width = useObservableState(useCallback(() => _width, []), stateChangedObservable);
|
|
10186
|
+
const handleWidthChange = (_, data) => {
|
|
10187
|
+
setWidth(data.value);
|
|
10188
|
+
};
|
|
10189
|
+
return (jsx("div", { className: classes.settingsContainer, children: jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Size: ", width] }), jsx(Slider, { min: 1, max: 100, value: width, onChange: handleWidthChange })] }) }));
|
|
10190
|
+
},
|
|
10191
|
+
};
|
|
10192
|
+
},
|
|
10193
|
+
};
|
|
10194
|
+
|
|
10195
|
+
/**
|
|
10196
|
+
* Rectangle selection tool for selecting regions of the texture
|
|
10197
|
+
*/
|
|
10198
|
+
const RectangleSelect = {
|
|
10199
|
+
name: "Rectangle Select",
|
|
10200
|
+
order: 100,
|
|
10201
|
+
icon: () => jsx(SelectObjectRegular, {}),
|
|
10202
|
+
cursor: "crosshair",
|
|
10203
|
+
getTool: (context) => {
|
|
10204
|
+
let pointerObserver = null;
|
|
10205
|
+
let isSelecting = false;
|
|
10206
|
+
let xStart = -1;
|
|
10207
|
+
let yStart = -1;
|
|
10208
|
+
return {
|
|
10209
|
+
activate: () => {
|
|
10210
|
+
const { scene } = context.getParameters();
|
|
10211
|
+
pointerObserver = scene.onPointerObservable.add((pointerInfo) => {
|
|
10212
|
+
const { getMouseCoordinates, setMetadata, metadata } = context.getParameters();
|
|
10213
|
+
if (!isSelecting) {
|
|
10214
|
+
if (pointerInfo.type === PointerEventTypes.POINTERDOWN &&
|
|
10215
|
+
pointerInfo &&
|
|
10216
|
+
pointerInfo.event.buttons === 1 &&
|
|
10217
|
+
context.getParameters().interactionEnabled() &&
|
|
10218
|
+
pointerInfo.pickInfo?.hit) {
|
|
10219
|
+
isSelecting = true;
|
|
10220
|
+
const { x, y } = ({ x: xStart, y: yStart } = getMouseCoordinates(pointerInfo));
|
|
10221
|
+
setMetadata({
|
|
10222
|
+
select: {
|
|
10223
|
+
x1: x,
|
|
10224
|
+
y1: y,
|
|
10225
|
+
x2: x,
|
|
10226
|
+
y2: y,
|
|
10227
|
+
},
|
|
10228
|
+
});
|
|
10229
|
+
}
|
|
10230
|
+
}
|
|
10231
|
+
else {
|
|
10232
|
+
if (pointerInfo.event.buttons !== 1 || !context.getParameters().interactionEnabled()) {
|
|
10233
|
+
isSelecting = false;
|
|
10234
|
+
if (metadata.select.x1 === metadata.select.x2 || metadata.select.y1 === metadata.select.y2) {
|
|
10235
|
+
setMetadata({
|
|
10236
|
+
select: {
|
|
10237
|
+
x1: -1,
|
|
10238
|
+
y1: -1,
|
|
10239
|
+
x2: -1,
|
|
10240
|
+
y2: -1,
|
|
10241
|
+
},
|
|
10242
|
+
});
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
else {
|
|
10246
|
+
if (pointerInfo.pickInfo?.hit && pointerInfo.type === PointerEventTypes.POINTERMOVE) {
|
|
10247
|
+
if (pointerInfo.type === PointerEventTypes.POINTERMOVE && isSelecting) {
|
|
10248
|
+
const { x, y } = getMouseCoordinates(pointerInfo);
|
|
10249
|
+
setMetadata({
|
|
10250
|
+
select: {
|
|
10251
|
+
x1: Math.min(x, xStart),
|
|
10252
|
+
y1: Math.min(y, yStart),
|
|
10253
|
+
x2: Math.max(x, xStart),
|
|
10254
|
+
y2: Math.max(y, yStart),
|
|
10255
|
+
},
|
|
10256
|
+
});
|
|
10257
|
+
}
|
|
10258
|
+
}
|
|
10259
|
+
}
|
|
10260
|
+
}
|
|
10261
|
+
});
|
|
10262
|
+
},
|
|
10263
|
+
deactivate() {
|
|
10264
|
+
isSelecting = false;
|
|
10265
|
+
pointerObserver?.remove();
|
|
10266
|
+
},
|
|
10267
|
+
};
|
|
10268
|
+
},
|
|
10269
|
+
};
|
|
10270
|
+
|
|
10271
|
+
const TextureEditorServiceIdentity = Symbol("TextureEditorService");
|
|
10272
|
+
const TextureEditorServiceDefinition = {
|
|
10273
|
+
friendlyName: "Texture Editor",
|
|
10274
|
+
produces: [TextureEditorServiceIdentity],
|
|
10275
|
+
factory: () => {
|
|
10276
|
+
const toolsCollection = new ObservableCollection();
|
|
10277
|
+
// Add the default tools.
|
|
10278
|
+
toolsCollection.add(RectangleSelect);
|
|
10279
|
+
toolsCollection.add(Paintbrush);
|
|
10280
|
+
toolsCollection.add(Eyedropper);
|
|
10281
|
+
toolsCollection.add(Floodfill);
|
|
10282
|
+
toolsCollection.add(Contrast);
|
|
10283
|
+
return {
|
|
10284
|
+
addTool: (toolProvider) => toolsCollection.add(toolProvider),
|
|
10285
|
+
component: (props) => {
|
|
10286
|
+
const tools = useOrderedObservableCollection(toolsCollection);
|
|
10287
|
+
return jsx(TextureEditor, { ...props, toolProviders: tools });
|
|
10288
|
+
},
|
|
10289
|
+
};
|
|
10290
|
+
},
|
|
10291
|
+
};
|
|
10292
|
+
|
|
10293
|
+
// Don't use instanceof in this case as we don't want to bring in the gui package just to check if the entity is an AdvancedDynamicTexture.
|
|
10294
|
+
function IsAdvancedDynamicTexture$1(entity) {
|
|
10295
|
+
return entity?.getClassName?.() === "AdvancedDynamicTexture";
|
|
10296
|
+
}
|
|
10297
|
+
const TexturePropertiesServiceDefinition = {
|
|
10298
|
+
friendlyName: "Texture Properties",
|
|
10299
|
+
consumes: [PropertiesServiceIdentity, SettingsContextIdentity, TextureEditorServiceIdentity],
|
|
10300
|
+
factory: (propertiesService, settingsContext, textureEditorService) => {
|
|
8550
10301
|
const baseTextureContentRegistration = propertiesService.addSectionContent({
|
|
8551
10302
|
key: "Base Texture Properties",
|
|
8552
10303
|
predicate: (entity) => entity instanceof BaseTexture,
|
|
8553
10304
|
content: [
|
|
8554
10305
|
{
|
|
8555
10306
|
section: "Preview",
|
|
8556
|
-
component: ({ context }) => jsx(BaseTexturePreviewProperties, { texture: context }),
|
|
10307
|
+
component: ({ context }) => jsx(BaseTexturePreviewProperties, { texture: context, textureEditor: textureEditorService.component }),
|
|
8557
10308
|
},
|
|
8558
10309
|
{
|
|
8559
10310
|
section: "General",
|
|
@@ -9832,6 +11583,8 @@ function ShowInspector(scene, options = {}) {
|
|
|
9832
11583
|
AnimationGroupPropertiesServiceDefinition,
|
|
9833
11584
|
MetadataPropertiesServiceDefinition,
|
|
9834
11585
|
AtmospherePropertiesServiceDefinition,
|
|
11586
|
+
// Texture editor and related services.
|
|
11587
|
+
TextureEditorServiceDefinition,
|
|
9835
11588
|
// Debug pane tab and related services.
|
|
9836
11589
|
DebugServiceDefinition,
|
|
9837
11590
|
// Stats pane tab and related services.
|
|
@@ -10666,5 +12419,5 @@ const TextAreaPropertyLine = (props) => {
|
|
|
10666
12419
|
// Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
|
|
10667
12420
|
AttachDebugLayer();
|
|
10668
12421
|
|
|
10669
|
-
export {
|
|
10670
|
-
//# sourceMappingURL=index-
|
|
12422
|
+
export { useSidePaneDockOverrides as $, Accordion as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, useVector3Property as G, useColor3Property as H, Inspector as I, useColor4Property as J, useQuaternionProperty as K, Link as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, MakePropertyHook as O, Popover as P, useInterceptObservable as Q, useEventfulState as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, useObservableCollection as U, Vector3PropertyLine as V, useOrderedObservableCollection as W, usePollingObservable as X, useResource as Y, useAsyncResource as Z, useCompactMode as _, SyncedSliderPropertyLine as a, useAngleConverters as a0, MakeTeachingMoment as a1, MakeDialogTeachingMoment as a2, InterceptFunction as a3, GetPropertyDescriptor as a4, IsPropertyReadonly as a5, InterceptProperty as a6, ObservableCollection as a7, ConstructorFactory as a8, SelectionServiceIdentity as a9, TextInput as aA, ToggleButton as aB, ChildWindow as aC, FactorGradientList as aD, Color3GradientList as aE, Color4GradientList as aF, Pane as aG, BooleanBadgePropertyLine as aH, Color3PropertyLine as aI, Color4PropertyLine as aJ, HexPropertyLine as aK, NumberInputPropertyLine as aL, LinkPropertyLine as aM, PropertyLine as aN, LineContainer as aO, PlaceholderPropertyLine as aP, StringifiedPropertyLine as aQ, TextAreaPropertyLine as aR, TextPropertyLine as aS, RotationVectorPropertyLine as aT, QuaternionPropertyLine as aU, Vector2PropertyLine as aV, Vector4PropertyLine as aW, SelectionServiceDefinition as aa, SettingsContextIdentity as ab, ShowInspector as ac, Checkbox as ad, ColorPickerPopup as ae, InputHexField as af, InputHsvField as ag, ComboBox as ah, DraggableLine as ai, Dropdown as aj, NumberDropdown as ak, StringDropdown as al, FactorGradientComponent as am, Color3GradientComponent as an, Color4GradientComponent as ao, ColorStepGradientComponent as ap, InfoLabel as aq, List as ar, MessageBar as as, PositionedPopover as at, SearchBar as au, SearchBox as av, SpinButton as aw, Switch as ax, SyncedSliderInput as ay, Textarea as az, Button as b, TextInputPropertyLine as c, SpinButtonPropertyLine as d, CheckboxPropertyLine as e, ShellServiceIdentity as f, SceneContextIdentity as g, AccordionSection as h, useExtensionManager as i, MakePopoverTeachingMoment as j, TeachingMoment as k, SidePaneContainer as l, PropertiesServiceIdentity as m, SceneExplorerServiceIdentity as n, SettingsServiceIdentity as o, StatsServiceIdentity as p, ConvertOptions as q, AttachDebugLayer as r, DetachDebugLayer as s, StringDropdownPropertyLine as t, useObservableState as u, BoundProperty as v, LinkToEntityPropertyLine as w, Theme as x, BuiltInsExtensionFeed as y, useProperty as z };
|
|
12423
|
+
//# sourceMappingURL=index-2Bq-qBwV.js.map
|