@babylonjs/inspector 8.29.1-preview → 8.29.2-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.
@@ -3,8 +3,8 @@ import { useMemo, useEffect, useState, useRef, useCallback, forwardRef, createCo
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, tokens, Link, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, ToggleButton as ToggleButton$1, Button as Button$1, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle1, AccordionPanel, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, TabList, Divider, Tooltip, Title3, Tab, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, DialogContent, SplitButton, MenuItemRadio, createLightTheme, createDarkTheme, FluentProvider, DialogActions, List as List$1, ListItem, useId, SpinButton as SpinButton$1, Slider, Input, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Badge, Dropdown as Dropdown$1, Option, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, Textarea as Textarea$1, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
7
- import { SubtractFilled, AddFilled, CopyRegular, PanelLeftExpandRegular, PanelLeftContractRegular, PanelRightExpandRegular, PanelRightContractRegular, DocumentTextRegular, FilterRegular, MoviesAndTvRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, AppsAddInRegular, DismissRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowMoveRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, GlobeRegular, SaveRegular, ArrowUndoRegular, ClearFormattingRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, PaintBrushRegular, BoxRegular, BranchRegular, CameraRegular, LightbulbRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, Cone16Filled, Cone16Regular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, WeatherSunnyLowFilled, DeleteFilled } from '@fluentui/react-icons';
6
+ import { makeStyles, ToggleButton as ToggleButton$1, Button as Button$1, tokens, Link, InfoLabel as InfoLabel$1, Body1Stronger, Checkbox as Checkbox$1, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Body1Strong, TabList, Tooltip, Title3, Tab, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, DialogContent, SplitButton, MenuItemRadio, createLightTheme, createDarkTheme, FluentProvider, DialogActions, List as List$1, ListItem, useId, SpinButton as SpinButton$1, Slider, Input, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Badge, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, Dropdown as Dropdown$1, Option, Textarea as Textarea$1, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
7
+ import { ChevronCircleRight20Regular, ChevronCircleDown20Regular, CopyRegular, PanelLeftExpandRegular, PanelLeftContractRegular, PanelRightExpandRegular, PanelRightContractRegular, DocumentTextRegular, FilterRegular, MoviesAndTvRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, AppsAddInRegular, DismissRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowMoveRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, GlobeRegular, SaveRegular, ArrowUndoRegular, ClearFormattingRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, PaintBrushRegular, BoxRegular, BranchRegular, CameraRegular, LightbulbRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, Cone16Filled, Cone16Regular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, DeleteFilled } from '@fluentui/react-icons';
8
8
  import { Collapse as Collapse$1 } from '@fluentui/react-motion-components-preview';
9
9
  import '@babylonjs/core/Misc/typeStore.js';
10
10
  import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
@@ -46,6 +46,7 @@ import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh.js';
46
46
  import { Node } from '@babylonjs/core/node.js';
47
47
  import { AnimationGroup, TargetedAnimation } from '@babylonjs/core/Animations/animationGroup.js';
48
48
  import { AnimationPropertiesOverride } from '@babylonjs/core/Animations/animationPropertiesOverride.js';
49
+ import { Atmosphere } from '@babylonjs/addons/atmosphere/atmosphere.js';
49
50
  import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera.js';
50
51
  import { FollowCamera } from '@babylonjs/core/Cameras/followCamera.js';
51
52
  import { FreeCamera } from '@babylonjs/core/Cameras/freeCamera.js';
@@ -99,7 +100,6 @@ import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineMa
99
100
  import '@babylonjs/core/Sprites/spriteSceneComponent.js';
100
101
  import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture.js';
101
102
  import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
102
- import { Atmosphere } from '@babylonjs/addons/atmosphere/atmosphere.js';
103
103
 
104
104
  const InterceptorHooksMaps$1 = new WeakMap();
105
105
  /**
@@ -631,15 +631,68 @@ function copyCommandToClipboard(strCommand) {
631
631
 
632
632
  const ToolContext = createContext({ useFluent: false, disableCopy: false, toolName: "" });
633
633
 
634
+ /**
635
+ * Toggles between two states using a button with icons.
636
+ * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change
637
+ *
638
+ * @param props
639
+ * @returns
640
+ */
641
+ const ToggleButton = (props) => {
642
+ const { value, onChange, title, appearance = "subtle" } = props;
643
+ const [checked, setChecked] = useState(value);
644
+ const toggle = useCallback(() => {
645
+ setChecked((prev) => {
646
+ const enabled = !prev;
647
+ onChange(enabled);
648
+ return enabled;
649
+ });
650
+ }, [setChecked]);
651
+ useEffect(() => {
652
+ setChecked(props.value);
653
+ }, [props.value]);
654
+ return (jsx(ToggleButton$1, { title: title, icon: checked ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: checked, onClick: toggle }));
655
+ };
656
+
657
+ const Button = (props) => {
658
+ // eslint-disable-next-line @typescript-eslint/naming-convention
659
+ const { icon: Icon, label, ...buttonProps } = props;
660
+ return (jsx(Button$1, { iconPosition: "after", ...buttonProps, icon: Icon && jsx(Icon, {}), children: label && props.label }));
661
+ };
662
+
663
+ const CustomTokens = {
664
+ inputWidth: "150px",
665
+ lineHeight: "36px",
666
+ labelMinWidth: "50px",
667
+ sliderMinWidth: "30px",
668
+ sliderMaxWidth: "80px",
669
+ rightAlignOffset: "-8px",
670
+ };
671
+ const UniformWidthStyling = { width: CustomTokens.inputWidth, textAlign: "right", boxSizing: "border-box" };
672
+ const useInputStyles$1 = makeStyles({
673
+ invalid: { backgroundColor: tokens.colorPaletteRedBackground2, ...UniformWidthStyling },
674
+ valid: UniformWidthStyling,
675
+ });
676
+ function HandleOnBlur(event) {
677
+ event.stopPropagation();
678
+ event.preventDefault();
679
+ }
680
+ function HandleKeyDown(event) {
681
+ event.stopPropagation(); // Prevent event propagation
682
+ // Prevent Enter key from causing form submission or value reversion
683
+ if (event.key === "Enter") {
684
+ event.preventDefault();
685
+ }
686
+ }
687
+
634
688
  const usePropertyLineStyles = makeStyles({
635
689
  container: {
636
690
  width: "100%",
637
691
  display: "flex",
638
692
  flexDirection: "column", // Stack line + expanded content
639
- padding: `${tokens.spacingVerticalXS} 0px`,
640
- borderBottom: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
641
693
  },
642
- line: {
694
+ baseLine: {
695
+ height: CustomTokens.lineHeight,
643
696
  display: "flex",
644
697
  alignItems: "center",
645
698
  justifyContent: "flex-start",
@@ -647,9 +700,8 @@ const usePropertyLineStyles = makeStyles({
647
700
  },
648
701
  label: {
649
702
  flex: "1 1 0", // grow=1, shrink =1, basis = 0 initial size before
650
- minWidth: "50px",
703
+ minWidth: CustomTokens.labelMinWidth,
651
704
  textAlign: "left",
652
- whiteSpace: "nowrap",
653
705
  overflow: "hidden",
654
706
  textOverflow: "ellipsis",
655
707
  },
@@ -661,31 +713,15 @@ const usePropertyLineStyles = makeStyles({
661
713
  display: "flex",
662
714
  alignItems: "center",
663
715
  justifyContent: "flex-end",
664
- gap: tokens.spacingHorizontalS,
665
- },
666
- button: {
667
- marginLeft: tokens.spacingHorizontalXXS,
668
- margin: 0,
669
- padding: 0,
670
- border: 0,
671
- minWidth: 0,
672
- },
673
- fillRestOfRightContentWidth: {
674
- flex: 1,
675
- display: "flex",
676
- justifyContent: "flex-end",
677
- alignItems: "center",
678
- },
679
- expandButton: {
680
- margin: 0,
681
- },
682
- expandedContent: {
683
- paddingLeft: "20px",
716
+ minWidth: "50%",
684
717
  },
685
718
  infoPopup: {
686
719
  whiteSpace: "normal",
687
720
  wordBreak: "break-word",
688
721
  },
722
+ copy: {
723
+ marginRight: CustomTokens.rightAlignOffset, // Accounts for the padding baked into fluent button / ensures propertyLine looks visually aligned at the right
724
+ },
689
725
  });
690
726
  /**
691
727
  * A reusable component that renders a property line with a label and child content, and an optional description, copy button, and expandable section.
@@ -710,7 +746,7 @@ const PropertyLine = forwardRef((props, ref) => {
710
746
  defaultValue: undefined, // Don't pass defaultValue to children as there is no guarantee how this will be used and we can't mix controlled + uncontrolled state
711
747
  })
712
748
  : children;
713
- return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.line, children: [jsx(InfoLabel$1, { className: classes.label, info: description ? jsx("div", { className: classes.infoPopup, children: description }) : undefined, title: label, children: jsx(Body1Strong, { className: classes.labelText, children: label }) }), jsxs("div", { className: classes.rightContent, children: [nullable && !ignoreNullable && (
749
+ return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.baseLine, children: [jsx(InfoLabel$1, { className: classes.label, info: description ? jsx("div", { className: classes.infoPopup, children: description }) : undefined, title: label, children: jsx(Body1Stronger, { className: classes.labelText, children: label }) }), jsxs("div", { className: classes.rightContent, children: [expandedContent && (jsx(ToggleButton, { title: "Expand/Collapse property", appearance: "transparent", checkedIcon: ChevronCircleDown20Regular, uncheckedIcon: ChevronCircleRight20Regular, value: expanded === true, onChange: setExpanded })), nullable && !ignoreNullable && (
714
750
  // If this is a nullableProperty and ignoreNullable was not sent, display a checkbox used to toggle null ('checked' means 'non null')
715
751
  jsx(Checkbox$1, { checked: !(props.value == null), onChange: (_, data) => {
716
752
  if (data.checked) {
@@ -722,10 +758,7 @@ const PropertyLine = forwardRef((props, ref) => {
722
758
  cachedVal.current = props.value;
723
759
  props.onChange(null);
724
760
  }
725
- }, title: "Toggle null state" })), jsx("div", { className: classes.fillRestOfRightContentWidth, children: processedChildren }), expandedContent && (jsx(ToggleButton$1, { appearance: "transparent", icon: expanded ? jsx(SubtractFilled, {}) : jsx(AddFilled, {}), className: classes.button, checked: expanded, onClick: (e) => {
726
- e.stopPropagation();
727
- setExpanded((expanded) => !expanded);
728
- } })), onCopy && !disableCopy && (jsx(Button$1, { className: classes.button, id: "copyProperty", icon: jsx(CopyRegular, {}), onClick: () => copyCommandToClipboard(onCopy()), title: "Copy to clipboard" }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { className: classes.expandedContent, children: expandedContent }) }))] }));
761
+ }, title: "Toggle null state" })), processedChildren, onCopy && !disableCopy && (jsx(Button, { className: classes.copy, title: "Copy to clipboard", appearance: "transparent", icon: CopyRegular, onClick: () => copyCommandToClipboard(onCopy()) }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { children: expandedContent }) }))] }));
729
762
  });
730
763
  const LineContainer = forwardRef((props, ref) => {
731
764
  const classes = usePropertyLineStyles();
@@ -741,7 +774,7 @@ const PlaceholderPropertyLine = (props) => {
741
774
  * @returns property-line wrapped link
742
775
  */
743
776
  const LinkPropertyLine = (props) => {
744
- return (jsx(PropertyLine, { ...props, children: jsx(Link, { inline: true, appearance: "subtle", onClick: () => props.onLink?.(), href: props.url, title: props.title, children: jsx(Body1, { children: props.value }) }) }));
777
+ return (jsx(PropertyLine, { ...props, children: jsx(Link, { inline: true, onClick: () => props.onLink?.(), href: props.url, title: props.title, children: jsx(Body1, { children: props.value }) }) }));
745
778
  };
746
779
 
747
780
  /**
@@ -763,12 +796,15 @@ const useStyles$e = makeStyles({
763
796
  accordion: {
764
797
  overflowX: "hidden",
765
798
  overflowY: "auto",
766
- paddingBottom: tokens.spacingVerticalM,
799
+ paddingTop: tokens.spacingVerticalM, // ensures the first section header has the same padding as the others (due to divider)
767
800
  display: "flex",
768
801
  flexDirection: "column",
769
- rowGap: tokens.spacingVerticalM,
770
802
  height: "100%",
771
803
  },
804
+ divider: {
805
+ paddingTop: "10px",
806
+ paddingBottom: "10px",
807
+ },
772
808
  panelDiv: {
773
809
  display: "flex",
774
810
  flexDirection: "column",
@@ -822,7 +858,7 @@ const Accordion = (props) => {
822
858
  }
823
859
  }, []);
824
860
  return (jsx(Accordion$1, { className: classes.accordion, collapsible: true, multiple: true, onToggle: onToggle, openItems: openItems, ...rest, children: validChildren.map((child) => {
825
- return (jsxs(AccordionItem, { value: child.title, children: [jsx(AccordionHeader, { expandIconPosition: "end", children: jsx(Subtitle1, { children: child.title }) }), jsx(AccordionPanel, { children: jsx("div", { className: classes.panelDiv, children: child.content }) })] }, child.content.key));
861
+ return (jsxs(AccordionItem, { value: child.title, children: [jsx(AccordionHeader, { children: jsx(Subtitle2Stronger, { children: child.title }) }), jsx(AccordionPanel, { children: jsx("div", { className: classes.panelDiv, children: child.content }) }), jsx(Divider, { inset: true, className: classes.divider })] }, child.content.key));
826
862
  }) }));
827
863
  };
828
864
 
@@ -956,6 +992,32 @@ const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, de
956
992
  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 })] }) }));
957
993
  };
958
994
 
995
+ /**
996
+ * A simple extension feed implementation that provides a fixed set of "built in" extensions.
997
+ * "Built in" in this context means extensions that are known at bundling time, and included
998
+ * in the bundle. Each extension can be dynamically imported so they are split into separate
999
+ * bundle chunks and downloaded only when first installed.
1000
+ */
1001
+ class BuiltInsExtensionFeed {
1002
+ constructor(name, extensions) {
1003
+ this.name = name;
1004
+ this._extensions = Array.from(extensions);
1005
+ }
1006
+ async queryExtensionsAsync(filter) {
1007
+ const filteredExtensions = filter ? this._extensions.filter((extension) => extension.name.includes(filter)) : this._extensions;
1008
+ return {
1009
+ totalCount: filteredExtensions.length,
1010
+ getExtensionMetadataAsync: async (index, count) => {
1011
+ return filteredExtensions.slice(index, index + count);
1012
+ },
1013
+ };
1014
+ }
1015
+ async getExtensionModuleAsync(name) {
1016
+ const extension = this._extensions.find((ext) => ext.name === name);
1017
+ return extension ? await extension.getExtensionModuleAsync() : undefined;
1018
+ }
1019
+ }
1020
+
959
1021
  /**
960
1022
  * Creates a polling observable that notifies its observers at a specified interval.
961
1023
  * @param delay The polling interval in milliseconds.
@@ -1506,7 +1568,7 @@ function usePane(alignment, defaultWidth, minWidth, topPaneComponents, bottomPan
1506
1568
  });
1507
1569
  // This memoizes the pane itself, which may or may not include the tab list, depending on the toolbar mode.
1508
1570
  const pane = useMemo(() => {
1509
- return (jsx(Fragment, { children: (topPaneComponents.length > 0 || bottomPaneComponents.length > 0) && (jsxs("div", { className: `${classes.pane} ${alignment === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsxs("div", { className: classes.paneContainer, style: { width: `${width}px` }, children: [toolbarMode === "compact" && (topPaneComponents.length > 1 || topBarComponents.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [topPaneTabList, jsx(Toolbar, { location: "top", components: topBarComponents })] }) })), jsxs("div", { className: classes.paneContent, children: [topSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: topSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, topSelectedTab?.content && jsx(topSelectedTab.content, {})] }), topPaneComponents.length > 0 && bottomPaneComponents.length > 0 && (jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.headerDivider, style: { margin: "0", minHeight: tokens.spacingVerticalM, cursor: "ns-resize" } })), bottomPaneComponents.length > 1 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), jsxs("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px,calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: [bottomSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: bottomSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, bottomSelectedTab?.content && jsx(bottomSelectedTab.content, {})] }), toolbarMode === "compact" && bottomBarComponents.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarComponents }) }) }))] }) }), jsx("div", { className: `${classes.resizer} ${alignment === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` }, onPointerDown: onResizerPointerDown })] })) }));
1571
+ return (jsx(Fragment, { children: (topPaneComponents.length > 0 || bottomPaneComponents.length > 0) && (jsxs("div", { className: `${classes.pane} ${alignment === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsxs("div", { className: classes.paneContainer, style: { width: `${width}px` }, children: [toolbarMode === "compact" && (topPaneComponents.length > 1 || topBarComponents.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [topPaneTabList, jsx(Toolbar, { location: "top", components: topBarComponents })] }) })), jsxs("div", { className: classes.paneContent, children: [topSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: topSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, topSelectedTab?.content && jsx(topSelectedTab.content, {})] }), topPaneComponents.length > 0 && bottomPaneComponents.length > 0 && (jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.headerDivider, style: { margin: "0", minHeight: tokens.spacingVerticalM, cursor: "ns-resize" } })), bottomPaneComponents.length > 1 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), bottomPaneComponents.length > 0 && (jsxs("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px,calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: [bottomSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: bottomSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, bottomSelectedTab?.content && jsx(bottomSelectedTab.content, {})] })), toolbarMode === "compact" && bottomBarComponents.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarComponents }) }) }))] }) }), jsx("div", { className: `${classes.resizer} ${alignment === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` }, onPointerDown: onResizerPointerDown })] })) }));
1510
1572
  }, [topPaneComponents, topSelectedTab, bottomPaneComponents, bottomSelectedTab, collapsed, width, resizing]);
1511
1573
  return [topPaneTabList, pane];
1512
1574
  }
@@ -1662,29 +1724,6 @@ const PropertiesServiceDefinition = {
1662
1724
  },
1663
1725
  };
1664
1726
 
1665
- /**
1666
- * Toggles between two states using a button with icons.
1667
- * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change
1668
- *
1669
- * @param props
1670
- * @returns
1671
- */
1672
- const ToggleButton = (props) => {
1673
- const { value, onChange, title, appearance = "subtle" } = props;
1674
- const [checked, setChecked] = useState(value);
1675
- const toggle = useCallback(() => {
1676
- setChecked((prev) => {
1677
- const enabled = !prev;
1678
- onChange(enabled);
1679
- return enabled;
1680
- });
1681
- }, [setChecked]);
1682
- useEffect(() => {
1683
- setChecked(props.value);
1684
- }, [props.value]);
1685
- return (jsx(ToggleButton$1, { title: title, icon: checked ? jsx(props.enabledIcon, {}) : props.disabledIcon ? jsx(props.disabledIcon, {}) : jsx(props.enabledIcon, {}), appearance: appearance, checked: checked, onClick: toggle }));
1686
- };
1687
-
1688
1727
  /**
1689
1728
  * Performs a topological sort on a graph.
1690
1729
  * @param graph The set of nodes that make up the graph.
@@ -1811,7 +1850,7 @@ const ToggleCommand = (props) => {
1811
1850
  // eslint-disable-next-line @typescript-eslint/naming-convention
1812
1851
  const [displayName, Icon, isEnabled] = useObservableState(useCallback(() => [command.displayName, command.icon, command.isEnabled], [command]), command.onChange);
1813
1852
  // TODO-iv2: Consolidate icon prop passing approach for inspector and shared components
1814
- return jsx(ToggleButton, { appearance: "transparent", title: displayName, enabledIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) });
1853
+ return jsx(ToggleButton, { appearance: "transparent", title: displayName, checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) });
1815
1854
  };
1816
1855
  const SceneTreeItem = (props) => {
1817
1856
  const { isSelected, select } = props;
@@ -2479,18 +2518,6 @@ const FrameStepsStats = ({ context: scene }) => {
2479
2518
  return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Absolute FPS", value: absoluteFPS }, "AbsoluteFPS"), jsx(StringifiedPropertyLine, { label: "Meshes Selection", value: meshesSelection, precision: 2, units: "ms" }, "MeshesSelection"), jsx(StringifiedPropertyLine, { label: "Render Targets", value: renderTargets, precision: 2, units: "ms" }, "RenderTargets"), jsx(StringifiedPropertyLine, { label: "Particles", value: particles, precision: 2, units: "ms" }, "Particles"), jsx(StringifiedPropertyLine, { label: "Sprites", value: sprites, precision: 2, units: "ms" }, "Sprites"), jsx(StringifiedPropertyLine, { label: "Animations", value: animations, precision: 2, units: "ms" }, "Animations"), jsx(StringifiedPropertyLine, { label: "Physics", value: physics, precision: 2, units: "ms" }, "Physics"), jsx(StringifiedPropertyLine, { label: "Inter-Frame Time", value: interFrameTime, precision: 2, units: "ms" }, "InterFrameTime"), jsx(StringifiedPropertyLine, { label: "GPU Frame Time", value: gpuFrameTime, precision: 2, units: "ms" }, "GPUFrameTime"), jsx(StringifiedPropertyLine, { label: "GPU Frame Time (Average)", value: gpuFrameTimeAverage, precision: 2, units: "ms" }, "GPUFrameTimeAverage")] }));
2480
2519
  };
2481
2520
 
2482
- const useButtonLineStyles = makeStyles({
2483
- button: {
2484
- border: `1px solid ${tokens.colorBrandBackground}`,
2485
- },
2486
- });
2487
- const Button = (props) => {
2488
- const classes = useButtonLineStyles();
2489
- // eslint-disable-next-line @typescript-eslint/naming-convention
2490
- const { icon: Icon, ...buttonProps } = props;
2491
- return (jsx(Button$1, { iconPosition: "after", className: classes.button, ...buttonProps, icon: Icon && jsx(Icon, {}), children: jsx(Body1, { children: props.label }) }));
2492
- };
2493
-
2494
2521
  /**
2495
2522
  * Wraps a button with a label in a line container
2496
2523
  * @param props Button props plus a label
@@ -2781,59 +2808,35 @@ const ToolsServiceDefinition = {
2781
2808
  },
2782
2809
  };
2783
2810
 
2784
- // This file contains "built in" extensions. They are optional and installed/uninstalled by the user, but they are
2785
- // well-known at build time and the extension is "downloaded" by simply doing a dynamic import. This is different
2786
- // from future extension types that are built and published apart from the inspector, and are downloaded as an isolated script.
2787
- const CreationToolsExtensionMetadata = {
2788
- name: "Asset Creation"};
2789
- const ExportToolsExtensionMetadata = {
2790
- name: "Export Tools",
2791
- description: "Adds new features to enable exporting Babylon assets such as .gltf, .glb, .babylon, and more.",
2792
- keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
2793
- };
2794
- const CaptureToolsExtensionMetadata = {
2795
- name: "Capture Tools",
2796
- description: "Adds new features to enable capturing screenshots, GIFs, videos, and more.",
2797
- keywords: ["capture", "screenshot", "gif", "video", "tools"],
2798
- };
2799
- const ImportToolsExtensionMetadata = {
2800
- name: "Import Tools",
2801
- description: "Adds new features related to importing Babylon assets.",
2802
- keywords: ["import", "tools"],
2803
- };
2804
- const Extensions = [/*CreationToolsExtensionMetadata, */ ExportToolsExtensionMetadata, CaptureToolsExtensionMetadata, ImportToolsExtensionMetadata];
2805
2811
  /**
2806
- * @internal
2812
+ * Well-known default built in extensions for the Inspector.
2807
2813
  */
2808
- class BuiltInsExtensionFeed {
2809
- constructor() {
2810
- this.name = "Built-ins";
2811
- }
2812
- async queryExtensionsAsync(filter) {
2813
- const filteredExtensions = filter ? Extensions.filter((extension) => extension.name.includes(filter)) : Extensions;
2814
- return {
2815
- totalCount: filteredExtensions.length,
2816
- getExtensionMetadataAsync: async (index, count) => {
2817
- return filteredExtensions.slice(index, index + count);
2818
- },
2819
- };
2820
- }
2821
- async getExtensionModuleAsync(name) {
2822
- if (name === CreationToolsExtensionMetadata.name) {
2823
- return await import('./creationToolsService-CX79OLiq.js');
2824
- }
2825
- else if (name === ExportToolsExtensionMetadata.name) {
2826
- return await import('./exportService-CzzSbQnD.js');
2827
- }
2828
- else if (name === CaptureToolsExtensionMetadata.name) {
2829
- return await import('./captureService-4uUJw84S.js');
2830
- }
2831
- else if (name === ImportToolsExtensionMetadata.name) {
2832
- return await import('./importService-BJFsjp8c.js');
2833
- }
2834
- return undefined;
2835
- }
2836
- }
2814
+ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
2815
+ // {
2816
+ // name: "Asset Creation",
2817
+ // description: "Adds new features to enable creating Babylon assets such as node materials, flow graphs, and more.",
2818
+ // keywords: ["creation"],
2819
+ // getExtensionModuleAsync: async () => await import("../services/creationToolsService"),
2820
+ // },
2821
+ {
2822
+ name: "Export Tools",
2823
+ description: "Adds new features to enable exporting Babylon assets such as .gltf, .glb, .babylon, and more.",
2824
+ keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
2825
+ getExtensionModuleAsync: async () => await import('./exportService-DRm8glYV.js'),
2826
+ },
2827
+ {
2828
+ name: "Capture Tools",
2829
+ description: "Adds new features to enable capturing screenshots, GIFs, videos, and more.",
2830
+ keywords: ["capture", "screenshot", "gif", "video", "tools"],
2831
+ getExtensionModuleAsync: async () => await import('./captureService-BEferXko.js'),
2832
+ },
2833
+ {
2834
+ name: "Import Tools",
2835
+ description: "Adds new features related to importing Babylon assets.",
2836
+ keywords: ["import", "tools"],
2837
+ getExtensionModuleAsync: async () => await import('./importService-CUFQ5Mb2.js'),
2838
+ },
2839
+ ]);
2837
2840
 
2838
2841
  const ExtensionManagerContext = createContext(undefined);
2839
2842
  function useExtensionManager() {
@@ -3107,6 +3110,27 @@ class ExtensionManager {
3107
3110
  }
3108
3111
  }
3109
3112
 
3113
+ const ThemeModeStorageKey = `Babylon/Settings/ThemeMode`;
3114
+ /**
3115
+ * Custom hook to manage the theme mode (light/dark/auto).
3116
+ * @param modeOverride If specified, any previously stored theme mode will be replaced with this mode.
3117
+ * @returns An object containing the theme mode state and helper functions.
3118
+ */
3119
+ function useThemeMode(modeOverride) {
3120
+ const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
3121
+ defaultValue: modeOverride,
3122
+ initializeWithValue: !modeOverride,
3123
+ localStorageKey: ThemeModeStorageKey,
3124
+ });
3125
+ // If a modeOverride is provided, replace any previously stored mode.
3126
+ // Also make sure there is a stored value initially, even before changing the theme.
3127
+ // This way, other usages of this hook will get the correct initial value.
3128
+ if (modeOverride || !localStorage.getItem(ThemeModeStorageKey)) {
3129
+ localStorage.setItem(ThemeModeStorageKey, JSON.stringify(ternaryDarkMode));
3130
+ }
3131
+ return { isDarkMode, themeMode: ternaryDarkMode, setThemeMode: setTernaryDarkMode };
3132
+ }
3133
+
3110
3134
  // This sorts a set of service definitions based on their dependencies (e.g. a topological sort).
3111
3135
  function SortServiceDefinitions(serviceDefinitions) {
3112
3136
  const sortedServiceDefinitions = [];
@@ -3390,14 +3414,14 @@ const ThemeSelectorServiceDefinition = {
3390
3414
  order: -300,
3391
3415
  component: () => {
3392
3416
  const classes = useStyles$6();
3393
- const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode();
3417
+ const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
3394
3418
  const onSelectedThemeChange = useCallback((e, data) => {
3395
- setTernaryDarkMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
3419
+ setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
3396
3420
  }, []);
3397
3421
  const toggleTheme = useCallback(() => {
3398
- setTernaryDarkMode(isDarkMode ? "light" : "dark");
3422
+ setThemeMode(isDarkMode ? "light" : "dark");
3399
3423
  }, [isDarkMode]);
3400
- return (jsxs(Menu, { positioning: "below-end", checkedValues: { theme: [ternaryDarkMode] }, onCheckedValueChange: onSelectedThemeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Select Theme", relationship: "label", children: jsx(SplitButton, { className: classes.themeButton, menuButton: triggerProps, primaryActionButton: { onClick: toggleTheme }, size: "small", appearance: "secondary", shape: "circular", icon: isDarkMode ? jsx(WeatherSunnyRegular, {}) : jsx(WeatherMoonRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.themeMenuPopover, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "theme", value: "system", children: "System" }), jsx(MenuItemRadio, { name: "theme", value: "light", children: "Light" }), jsx(MenuItemRadio, { name: "theme", value: "dark", children: "Dark" })] }) })] }));
3424
+ return (jsxs(Menu, { positioning: "below-end", checkedValues: { theme: [themeMode] }, onCheckedValueChange: onSelectedThemeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Select Theme", relationship: "label", children: jsx(SplitButton, { className: classes.themeButton, menuButton: triggerProps, primaryActionButton: { onClick: toggleTheme }, size: "small", appearance: "secondary", shape: "circular", icon: isDarkMode ? jsx(WeatherSunnyRegular, {}) : jsx(WeatherMoonRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.themeMenuPopover, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "theme", value: "system", children: "System" }), jsx(MenuItemRadio, { name: "theme", value: "light", children: "Light" }), jsx(MenuItemRadio, { name: "theme", value: "dark", children: "Dark" })] }) })] }));
3401
3425
  },
3402
3426
  });
3403
3427
  return {
@@ -3466,11 +3490,11 @@ const useStyles$5 = makeStyles({
3466
3490
  * @returns A token that can be used to dispose of the tool.
3467
3491
  */
3468
3492
  function MakeModularTool(options) {
3469
- const { containerElement, serviceDefinitions, isThemeable = true, extensionFeeds = [] } = options;
3493
+ const { containerElement, serviceDefinitions, themeMode, showThemeSelector = true, extensionFeeds = [] } = options;
3470
3494
  const modularToolRootComponent = () => {
3471
3495
  const classes = useStyles$5();
3472
3496
  const [extensionManagerContext, setExtensionManagerContext] = useState();
3473
- const { isDarkMode } = useTernaryDarkMode();
3497
+ const { isDarkMode } = useThemeMode(themeMode);
3474
3498
  const [requiredExtensions, setRequiredExtensions] = useState();
3475
3499
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
3476
3500
  const [extensionInstallError, setExtensionInstallError] = useState();
@@ -3498,7 +3522,7 @@ function MakeModularTool(options) {
3498
3522
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
3499
3523
  }
3500
3524
  // Register the theme selector service (for selecting the theme) if theming is configured.
3501
- if (isThemeable) {
3525
+ if (showThemeSelector) {
3502
3526
  await serviceContainer.addServiceAsync(ThemeSelectorServiceDefinition);
3503
3527
  }
3504
3528
  // Register all external services (that make up a unique tool).
@@ -3770,7 +3794,7 @@ const GizmoToolbar = (props) => {
3770
3794
  const toggleCoordinatesMode = useCallback(() => {
3771
3795
  gizmoManager.coordinatesMode = coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? 0 /* GizmoCoordinatesMode.World */ : 1 /* GizmoCoordinatesMode.Local */;
3772
3796
  }, [gizmoManager, coordinatesMode]);
3773
- return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", enabledIcon: ArrowMoveRegular, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", enabledIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", enabledIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", enabledIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
3797
+ return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", checkedIcon: ArrowMoveRegular, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", checkedIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", checkedIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", checkedIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
3774
3798
  onClick: toggleCoordinatesMode,
3775
3799
  }, size: "small", appearance: "secondary", shape: "rounded", icon: coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? jsx(CubeRegular, {}) : jsx(GlobeRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.coordinatesModeMenu, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "coordinatesMode", value: 1 /* GizmoCoordinatesMode.Local */.toString(), children: "Local" }), jsx(MenuItemRadio, { name: "coordinatesMode", value: 0 /* GizmoCoordinatesMode.World */.toString(), children: "World" })] }) })] }) })] }));
3776
3800
  };
@@ -3804,31 +3828,11 @@ const TargetedAnimationGeneralProperties = (props) => {
3804
3828
  * @returns
3805
3829
  */
3806
3830
  const InfoLabel = (props) => {
3807
- return (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: props.info, children: props.label }));
3831
+ return (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: props.info, children: jsx(Body1, { children: props.label }) }));
3808
3832
  };
3809
3833
 
3810
- function HandleOnBlur(event) {
3811
- event.stopPropagation();
3812
- event.preventDefault();
3813
- }
3814
- function HandleKeyDown(event) {
3815
- event.stopPropagation(); // Prevent event propagation
3816
- // Prevent Enter key from causing form submission or value reversion
3817
- if (event.key === "Enter") {
3818
- event.preventDefault();
3819
- }
3820
- }
3821
-
3822
- const useSpinStyles = makeStyles({
3823
- base: {
3824
- display: "flex",
3825
- flexDirection: "column",
3826
- minWidth: "55px",
3827
- },
3828
- invalid: { backgroundColor: tokens.colorPaletteRedBackground2 },
3829
- });
3830
3834
  const SpinButton = (props) => {
3831
- const classes = useSpinStyles();
3835
+ const classes = useInputStyles$1();
3832
3836
  const { min, max } = props;
3833
3837
  const [value, setValue] = useState(props.value);
3834
3838
  const lastCommittedValue = useRef(props.value);
@@ -3870,7 +3874,7 @@ const SpinButton = (props) => {
3870
3874
  }
3871
3875
  };
3872
3876
  const id = useId("spin-button");
3873
- return (jsxs("div", { className: classes.base, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, step: step, id: id, size: "small", precision: CalculatePrecision(step ?? 1.01), displayValue: props.unit ? `${PrecisionRound(value, CalculatePrecision(step ?? 1.01))} ${props.unit}` : undefined, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: `${!validateValue(value) ? classes.invalid : ""}` })] }));
3877
+ return (jsxs("div", { children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, step: step, id: id, size: "medium", precision: CalculatePrecision(step ?? 1.01), displayValue: props.unit ? `${PrecisionRound(value, CalculatePrecision(step ?? 1.01))} ${props.unit}` : undefined, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: `${!validateValue(value) ? classes.invalid : classes.valid}` })] }));
3874
3878
  };
3875
3879
  /**
3876
3880
  * Fluent's CalculatePrecision function
@@ -3917,18 +3921,16 @@ function PrecisionRound(value, precision) {
3917
3921
  }
3918
3922
 
3919
3923
  const useSyncedSliderStyles = makeStyles({
3924
+ container: { display: "flex" },
3920
3925
  syncedSlider: {
3926
+ flex: "1 1 0",
3927
+ flexDirection: "row",
3921
3928
  display: "flex",
3922
3929
  alignItems: "center",
3923
- gap: tokens.spacingHorizontalXXS, // 2px
3924
- width: "100%", // Only fill available space
3925
3930
  },
3926
3931
  slider: {
3927
- flexGrow: 1, // Let slider grow
3928
- minWidth: "40px", // Minimum width for slider to remain usable
3929
- },
3930
- spinButton: {
3931
- width: "60px",
3932
+ minWidth: CustomTokens.sliderMinWidth, // Minimum width for slider to remain usable
3933
+ maxWidth: CustomTokens.sliderMaxWidth,
3932
3934
  },
3933
3935
  });
3934
3936
  /**
@@ -3937,6 +3939,7 @@ const useSyncedSliderStyles = makeStyles({
3937
3939
  * @returns SyncedSlider component
3938
3940
  */
3939
3941
  const SyncedSliderInput = (props) => {
3942
+ const { infoLabel, ...passthroughProps } = props;
3940
3943
  const classes = useSyncedSliderStyles();
3941
3944
  const [value, setValue] = useState(props.value);
3942
3945
  const pendingValueRef = useRef(undefined);
@@ -3976,7 +3979,7 @@ const SyncedSliderInput = (props) => {
3976
3979
  setValue(value);
3977
3980
  props.onChange(value); // Input always updates immediately
3978
3981
  };
3979
- return (jsxs("div", { className: classes.syncedSlider, children: [props.min !== undefined && props.max !== undefined && (jsx(Slider, { ...props, size: "small", className: classes.slider, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...props, className: classes.spinButton, value: Math.round(value / step) * step, onChange: handleInputChange, step: props.step })] }));
3982
+ return (jsxs("div", { className: classes.container, children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [props.min !== undefined && props.max !== undefined && (jsx(Slider, { ...passthroughProps, className: classes.slider, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, value: Math.round(value / step) * step, onChange: handleInputChange, step: props.step })] })] }));
3980
3983
  };
3981
3984
 
3982
3985
  /**
@@ -3989,14 +3992,6 @@ const SyncedSliderPropertyLine = forwardRef((props, ref) => {
3989
3992
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
3990
3993
  });
3991
3994
 
3992
- const useInputStyles$1 = makeStyles({
3993
- base: {
3994
- display: "flex",
3995
- flexDirection: "column",
3996
- width: "100px",
3997
- },
3998
- invalid: { backgroundColor: tokens.colorPaletteRedBackground2 },
3999
- });
4000
3995
  const TextInput = (props) => {
4001
3996
  const classes = useInputStyles$1();
4002
3997
  const [value, setValue] = useState(props.value);
@@ -4029,7 +4024,7 @@ const TextInput = (props) => {
4029
4024
  tryCommitValue(event.currentTarget.value);
4030
4025
  };
4031
4026
  const id = useId("input-button");
4032
- return (jsxs("div", { className: classes.base, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, id: id, size: "small", value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: `${!validateValue(value) ? classes.invalid : ""}` })] }));
4027
+ return (jsxs("div", { children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, id: id, size: "medium", value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: `${!validateValue(value) ? classes.invalid : classes.valid}` })] }));
4033
4028
  };
4034
4029
 
4035
4030
  /**
@@ -4241,7 +4236,7 @@ const TensorPropertyLine = (props) => {
4241
4236
  setVector(newVector);
4242
4237
  props.onChange(newVector);
4243
4238
  };
4244
- return (jsx(PropertyLine, { ...props, onCopy: () => `(${vector.x}, ${vector.y} ${HasZ(vector) ? `, ${vector.z}` : ""}${HasW(vector) ? `, ${vector.w}` : ""})`, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: props.unit, step: props.step }), jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: props.unit, step: props.step }), HasZ(vector) && (jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: props.unit, step: props.step })), HasW(vector) && (jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: props.unit, step: props.step }))] }), children: jsx(Body1, { children: `X: ${formatted(props.value.x)} | Y: ${formatted(props.value.y)}${HasZ(props.value) ? ` | Z: ${formatted(props.value.z)}` : ""}${HasW(props.value) ? ` | W: ${formatted(props.value.w)}` : ""}` }) }));
4239
+ return (jsx(PropertyLine, { ...props, onCopy: () => `new ${props.value.constructor.name}(${vector.x},${vector.y} ${HasZ(vector) ? `,${vector.z}` : ""}${HasW(vector) ? `,${vector.w}` : ""})`, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: props.unit, step: props.step }), jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: props.unit, step: props.step }), HasZ(vector) && (jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: props.unit, step: props.step })), HasW(vector) && (jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: props.unit, step: props.step }))] }), children: jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
4245
4240
  };
4246
4241
  const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
4247
4242
  const RotationVectorPropertyLine = (props) => {
@@ -4270,72 +4265,318 @@ const QuaternionPropertyLine = (props) => {
4270
4265
  const Vector2PropertyLine = TensorPropertyLine;
4271
4266
  const Vector3PropertyLine = TensorPropertyLine;
4272
4267
 
4273
- const ArcRotateCameraTransformProperties = (props) => {
4274
- const { camera, settings } = props;
4275
- const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters(settings);
4276
- const lowerAlphaLimit = useProperty(camera, "lowerAlphaLimit") ?? 0;
4277
- const upperAlphaLimit = useProperty(camera, "upperAlphaLimit") ?? Math.PI * 2;
4278
- const lowerBetaLimit = useProperty(camera, "lowerBetaLimit") ?? -Math.PI;
4279
- const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? Math.PI;
4280
- const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
4281
- const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
4282
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Alpha", description: `Horizontal angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "alpha", min: toDisplayAngle(lowerAlphaLimit), max: toDisplayAngle(upperAlphaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Beta", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "beta", min: toDisplayAngle(lowerBetaLimit), max: toDisplayAngle(upperBetaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), lowerRadiusLimit != null && upperRadiusLimit != null ? (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: lowerRadiusLimit, max: upperRadiusLimit, step: 0.01 })) : (jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: 0, step: 0.01 }))] }));
4283
- };
4284
- const ArcRotateCameraControlProperties = (props) => {
4285
- const { camera } = props;
4286
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angular Sensitivity X", target: camera, propertyKey: "angularSensibilityX" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angular Sensitivity Y", target: camera, propertyKey: "angularSensibilityY" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Panning Sensitivity", target: camera, propertyKey: "panningSensibility" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Pinch Delta Percentage", target: camera, propertyKey: "pinchDeltaPercentage" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Wheel Delta Percentage", target: camera, propertyKey: "wheelDeltaPercentage" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Natural Pinch Zoom", target: camera, propertyKey: "useNaturalPinchZoom" })] }));
4287
- };
4288
- const ArcRotateCameraCollisionProperties = (props) => {
4289
- const { camera } = props;
4290
- const collisionRadius = useProperty(camera, "collisionRadius");
4291
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Check Collisions", target: camera, propertyKey: "checkCollisions" }), jsx(Vector3PropertyLine, { label: "Collision Radius", value: collisionRadius, onChange: (val) => (camera.collisionRadius = val) })] }));
4292
- };
4293
- const ArcRotateCameraLimitsProperties = (props) => {
4294
- const { camera } = props;
4295
- // TODO-Iv2: Update defaultValues
4296
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Alpha Limit", target: camera, propertyKey: "lowerAlphaLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Alpha Limit", target: camera, propertyKey: "upperAlphaLimit", nullable: true, defaultValue: Infinity }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Beta Limit", target: camera, propertyKey: "lowerBetaLimit", nullable: true, defaultValue: -Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Beta Limit", target: camera, propertyKey: "upperBetaLimit", nullable: true, defaultValue: Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Radius Limit", target: camera, propertyKey: "lowerRadiusLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Radius Limit", target: camera, propertyKey: "upperRadiusLimit", nullable: true, defaultValue: 100 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Target Y Limit", target: camera, propertyKey: "lowerTargetYLimit" })] }));
4297
- };
4298
- const ArcRotateCameraBehaviorsProperties = (props) => {
4299
- const { camera } = props;
4300
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Auto Rotation", target: camera, propertyKey: "useAutoRotationBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Bouncing", target: camera, propertyKey: "useBouncingBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Framing", target: camera, propertyKey: "useFramingBehavior" })] }));
4301
- };
4302
-
4303
- const useDropdownStyles = makeStyles({
4304
- dropdownOption: {
4305
- textAlign: "right",
4306
- minWidth: "40px",
4268
+ const useColorPickerStyles = makeStyles({
4269
+ colorPickerContainer: {
4270
+ width: "325px",
4271
+ display: "flex",
4272
+ flexDirection: "column",
4273
+ gap: tokens.spacingVerticalMNudge, // 10px
4274
+ overflow: "visible",
4275
+ },
4276
+ previewColor: {
4277
+ width: "50px",
4278
+ height: "50px",
4279
+ borderRadius: tokens.borderRadiusMedium,
4280
+ border: `${tokens.spacingVerticalXXS} solid ${tokens.colorNeutralShadowKeyLighter}`,
4281
+ "@media (forced-colors: active)": {
4282
+ forcedColorAdjust: "none", // ensures elmement maintains color in high constrast mode
4283
+ },
4284
+ },
4285
+ row: {
4286
+ display: "flex",
4287
+ flexDirection: "row",
4288
+ gap: tokens.spacingVerticalM, // 12px
4289
+ alignItems: "center",
4290
+ },
4291
+ colorFieldWrapper: {
4292
+ display: "flex",
4293
+ flexDirection: "column",
4294
+ gap: tokens.spacingVerticalSNudge, // 6px
4295
+ },
4296
+ input: {
4297
+ width: "80px",
4298
+ },
4299
+ spinButton: {
4300
+ width: "50px",
4301
+ },
4302
+ container: {
4303
+ display: "flex",
4304
+ flexDirection: "column",
4305
+ gap: tokens.spacingVerticalL, // 16px
4307
4306
  },
4308
- optionsLine: {},
4309
4307
  });
4310
- /**
4311
- * Renders a fluent UI dropdown component for the options passed in, and an additional 'Not Defined' option if null is set to true
4312
- * This component can handle both null and undefined values
4313
- * @param props
4314
- * @returns dropdown component
4315
- */
4316
- const Dropdown = (props) => {
4317
- const classes = useDropdownStyles();
4318
- const { options, value } = props;
4319
- const [defaultVal, setDefaultVal] = useState(props.value);
4308
+ const ColorPickerPopup = (props) => {
4309
+ const classes = useColorPickerStyles();
4310
+ const [color, setColor] = useState(props.value);
4311
+ const [popoverOpen, setPopoverOpen] = useState(false);
4320
4312
  useEffect(() => {
4321
- setDefaultVal(value);
4313
+ setColor(props.value); // Ensures the trigger color updates when props.value changes
4322
4314
  }, [props.value]);
4323
- return (jsx(Dropdown$1, { disabled: props.disabled, size: "small", className: classes.dropdownOption, onOptionSelect: (evt, data) => {
4324
- const value = typeof props.value === "number" ? Number(data.optionValue) : data.optionValue;
4325
- if (value !== undefined) {
4326
- setDefaultVal(value);
4327
- props.onChange(value);
4328
- }
4329
- }, selectedOptions: [defaultVal.toString()], value: options.find((o) => o.value === defaultVal)?.label, children: options.map((option) => (jsx(Option, { className: classes.optionsLine, value: option.value.toString(), disabled: false, children: option.label }, option.label))) }));
4330
- };
4331
- const NumberDropdown = Dropdown;
4332
- const StringDropdown = Dropdown;
4333
-
4334
- /**
4335
- * Wraps a dropdown in a property line
4336
- * @param props - PropertyLineProps and DropdownProps
4337
- * @returns property-line wrapped dropdown
4338
- */
4315
+ const handleColorPickerChange = (_, data) => {
4316
+ let color = Color3.FromHSV(data.color.h, data.color.s, data.color.v);
4317
+ if (props.value instanceof Color4) {
4318
+ color = Color4.FromColor3(color, data.color.a ?? 1);
4319
+ }
4320
+ handleChange(color);
4321
+ };
4322
+ const handleChange = (newColor) => {
4323
+ setColor(newColor);
4324
+ props.onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
4325
+ };
4326
+ return (jsxs(Popover, { positioning: {
4327
+ align: "start",
4328
+ overflowBoundary: document.body,
4329
+ autoSize: true,
4330
+ }, open: popoverOpen, trapFocus: true, onOpenChange: (_, data) => setPopoverOpen(data.open), children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: "small", color: color.toHexString(), value: color.toHexString().slice(1) }) }), jsx(PopoverSurface, { children: jsxs("div", { className: classes.colorPickerContainer, children: [jsxs(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.container, children: [jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(InputHexField, { title: "Gamma Hex", value: color, isLinearMode: props.isLinearMode, onChange: handleChange }), jsx(InputHexField, { title: "Linear Hex", linearHex: true, isLinearMode: props.isLinearMode, value: color, onChange: handleChange })] }), jsxs("div", { className: classes.row, 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.row, 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 })] })] })] }) })] }));
4331
+ };
4332
+ const HEX_REGEX = RegExp(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/);
4333
+ /**
4334
+ * Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
4335
+ * When the hex color is changed by user, component calculates the new Color3/4 value and calls onChange
4336
+ *
4337
+ * Component uses the isLinearMode boolean to display an informative label regarding linear / gamma space
4338
+ * @param props - The properties for the InputHexField component.
4339
+ * @returns
4340
+ */
4341
+ const InputHexField = (props) => {
4342
+ const styles = useColorPickerStyles();
4343
+ const { title, value, onChange, linearHex, isLinearMode } = props;
4344
+ return (jsx("div", { className: styles.colorFieldWrapper, children: jsx(TextInput, { disabled: linearHex ? !isLinearMode : false, className: styles.input, value: linearHex ? value.toLinearSpace().toHexString() : value.toHexString(), validator: (val) => val != "" && HEX_REGEX.test(val), onChange: (val) => (linearHex ? onChange(Color3.FromHexString(val).toGammaSpace()) : onChange(Color3.FromHexString(val))), infoLabel: title
4345
+ ? {
4346
+ label: title,
4347
+ // If not representing a linearHex, no info is needed.
4348
+ info: !props.linearHex ? undefined : !isLinearMode ? ( // If representing a linear hex but we are in gammaMode, simple message explaining why linearHex is disabled
4349
+ jsx(Fragment, { children: " This color picker is attached to an entity whose color is stored in gamma space, so we are showing linear hex in disabled view " })) : (
4350
+ // If representing a linear hex and we are in linearMode, give information about how to use these hex values
4351
+ jsxs(Fragment, { children: ["This color picker is attached to an entity whose color is stored in linear space (ex: PBR Material), and Babylon converts the color to gamma space before rendering on screen because the human eye is best at processing colors in gamma space. We thus also want to display the color picker in gamma space so that the color chosen here will match the color seen in your entity.", jsx("br", {}), "If you want to copy/paste the HEX into your code, you can either use", jsx(Body1Strong, { children: "Color3.FromHexString(LINEAR_HEX)" }), jsx("br", {}), "or", jsx("br", {}), jsx(Body1Strong, { children: "Color3.FromHexString(GAMMA_HEX).toLinearSpace()" }), jsx("br", {}), jsx("br", {}), jsx(Link, { href: "https://doc.babylonjs.com/preparingArtForBabylon/controllingColorSpace/", children: " Read more in our docs! " })] })),
4352
+ }
4353
+ : undefined }) }));
4354
+ };
4355
+ const InputRgbField = (props) => {
4356
+ const { value, onChange, title, rgbKey } = props;
4357
+ const classes = useColorPickerStyles();
4358
+ const handleChange = useCallback((val) => {
4359
+ const newColor = value.clone();
4360
+ newColor[rgbKey] = val / 255.0; // Convert to 0-1 range
4361
+ onChange(newColor);
4362
+ }, [value, onChange, rgbKey]);
4363
+ return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { title: title, infoLabel: title ? { label: title } : undefined, className: classes.spinButton, min: 0, max: 255, value: Math.round(value[rgbKey] * 255), forceInt: true, onChange: handleChange }) }));
4364
+ };
4365
+ function rgbaToHsv(color) {
4366
+ const c = new Color3(color.r, color.g, color.b);
4367
+ const hsv = c.toHSV();
4368
+ return { h: hsv.r, s: hsv.g, v: hsv.b, a: color.a };
4369
+ }
4370
+ /**
4371
+ * In the HSV (Hue, Saturation, Value) color model, Hue (H) ranges from 0 to 360 degrees, representing the color's position on the color wheel.
4372
+ * Saturation (S) ranges from 0 to 100%, indicating the intensity or purity of the color, with 0 being shades of gray and 100 being a fully saturated color.
4373
+ * Value (V) ranges from 0 to 100%, representing the brightness of the color, with 0 being black and 100 being the brightest.
4374
+ * @param props - The properties for the InputHsvField component.
4375
+ */
4376
+ const InputHsvField = (props) => {
4377
+ const { value, title, hsvKey, max, onChange, scale = 1 } = props;
4378
+ const classes = useColorPickerStyles();
4379
+ const handleChange = useCallback((val) => {
4380
+ // Convert current color to HSV, update the new hsv value, then call onChange prop
4381
+ const hsv = rgbaToHsv(value);
4382
+ hsv[hsvKey] = val / scale;
4383
+ let newColor = Color3.FromHSV(hsv.h, hsv.s, hsv.v);
4384
+ if (value instanceof Color4) {
4385
+ newColor = Color4.FromColor3(newColor, value.a ?? 1);
4386
+ }
4387
+ props.onChange(newColor);
4388
+ }, [value, onChange, hsvKey, scale]);
4389
+ return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { infoLabel: title ? { label: title } : undefined, title: title, className: classes.spinButton, min: 0, max: max, value: Math.round(rgbaToHsv(value)[hsvKey] * scale), forceInt: true, onChange: handleChange }) }));
4390
+ };
4391
+ /**
4392
+ * Displays the alpha value of a color, either in the disabled state (if color is Color3) or as a spin button (if color is Color4).
4393
+ * @param props
4394
+ * @returns
4395
+ */
4396
+ const InputAlphaField = (props) => {
4397
+ const classes = useColorPickerStyles();
4398
+ const { color, onChange } = props;
4399
+ const handleChange = useCallback((value) => {
4400
+ if (Number.isNaN(value) || value < 0 || value > 1) {
4401
+ return;
4402
+ }
4403
+ if (color instanceof Color4) {
4404
+ const newColor = color.clone();
4405
+ newColor.a = value;
4406
+ return newColor;
4407
+ }
4408
+ else {
4409
+ return Color4.FromColor3(color, value);
4410
+ }
4411
+ }, [onChange]);
4412
+ return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { disabled: color instanceof Color3, min: 0, max: 1, className: classes.spinButton, value: color instanceof Color3 ? 1 : color.a, step: 0.01, onChange: handleChange, infoLabel: {
4413
+ label: "Alpha",
4414
+ info: color instanceof Color3 ? (jsx(Fragment, { children: "Because this color picker is representing a Color3, we do not permit modifying alpha from the color picker. You can however modify the entity's alpha property directly, either in code via entity.alpha OR via inspector's transparency section." })) : undefined,
4415
+ } }) }));
4416
+ };
4417
+
4418
+ /**
4419
+ * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
4420
+ * The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
4421
+ * @param props - PropertyLine props, replacing children with a color object so that we can properly display the color
4422
+ * @returns Component wrapping a colorPicker component with a property line
4423
+ */
4424
+ const ColorPropertyLine = forwardRef((props, ref) => {
4425
+ const [color, setColor] = useState(props.value);
4426
+ const onSliderChange = (value, key) => {
4427
+ let newColor;
4428
+ if (key === "a") {
4429
+ newColor = Color4.FromColor3(color, value);
4430
+ }
4431
+ else {
4432
+ newColor = color.clone();
4433
+ newColor[key] = value / 255;
4434
+ }
4435
+ setColor(newColor); // Create a new object to trigger re-render
4436
+ props.onChange(newColor);
4437
+ };
4438
+ const onColorPickerChange = (newColor) => {
4439
+ setColor(newColor);
4440
+ props.onChange(newColor);
4441
+ };
4442
+ return (jsx(PropertyLine, { ref: ref, ...props, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "b") }), color instanceof Color4 && jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, onChange: (value) => onSliderChange(value, "a") })] }), children: jsx(ColorPickerPopup, { ...props, onChange: onColorPickerChange, value: color }) }));
4443
+ });
4444
+ const Color3PropertyLine = ColorPropertyLine;
4445
+ const Color4PropertyLine = ColorPropertyLine;
4446
+
4447
+ const GeneralAtmosphereProperties = (props) => {
4448
+ const { entity: atmosphere } = props;
4449
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Planet Radius (km)", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "planetRadius", min: 1000.0, max: 10000.0, step: 1 }), jsx(BoundProperty, { label: "Atmosphere Thickness (km)", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "atmosphereThickness", min: 1.0, max: 200.0, step: 1 })] }));
4450
+ };
4451
+ const ScatteringAndAbsorptionProperties = (props) => {
4452
+ const { entity: atmosphere } = props;
4453
+ return (jsxs(Fragment, { children: [jsx(PropertyLine, { label: "Rayleigh Scattering", expandByDefault: true, description: "Rayleigh scattering is the scattering of light off of the molecules of the atmosphere. It is the main reason why the sky is blue. Increasing the Rayleigh scattering coefficient will result in a bluer sky.", expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "rayleighScatteringScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakRayleighScattering", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Mie Scattering", description: "Mie scattering is the scattering of light off of the larger particles in the atmosphere, such as dust and water droplets. It is responsible for the white appearance of clouds and the haziness of the sky. Increasing the Mie scattering coefficient will result in a whiter sky.", expandByDefault: true, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "mieScatteringScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakMieScattering", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Mie Absorption", description: "Mie absorption is the absorption of light by the larger particles in the atmosphere, such as dust and water droplets. It is responsible for the dimming of the sun during haze and fog. Increasing mie absorption coefficient will result in visually darker skies.", expandByDefault: true, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "mieAbsorptionScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakMieAbsorption", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Ozone Absorption", expandByDefault: true, description: "Ozone absorption is the absorption of light by ozone molecules in the atmosphere. Increasing ozone absorption coefficient will result in visually darker skies.", expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "ozoneAbsorptionScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakOzoneAbsorption", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01 })] }) })] }));
4454
+ };
4455
+ const MultipleScatteringProperties = (props) => {
4456
+ const { entity: atmosphere } = props;
4457
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "multiScatteringIntensity", min: 0, max: 5.0, step: 0.1 }), jsx(BoundProperty, { label: "Minimum Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "minimumMultiScatteringIntensity", min: 0.0, max: 0.1, step: 0.0001 }), jsx(BoundProperty, { label: "Minimum Color", component: Color3PropertyLine, target: atmosphere, propertyKey: "minimumMultiScatteringColor" }), jsx(BoundProperty, { label: "Ground Albedo", component: Color3PropertyLine, target: atmosphere, propertyKey: "groundAlbedo" })] }));
4458
+ };
4459
+ const AerialPerspectiveProperties = (props) => {
4460
+ const { entity: atmosphere } = props;
4461
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveIntensity", min: 0, max: 5.0, step: 0.1 }), jsx(BoundProperty, { label: "Transmittance Scale", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveTransmittanceScale", min: 0, max: 2.0, step: 0.01 }), jsx(BoundProperty, { label: "Saturation", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveSaturation", min: 0, max: 2.0, step: 0.01 })] }));
4462
+ };
4463
+ const DiffuseIrradianceProperties = (props) => {
4464
+ const { entity: atmosphere } = props;
4465
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "diffuseSkyIrradianceIntensity", min: 0, max: 5.0, step: 0.001 }), jsx(BoundProperty, { label: "Desaturation", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "diffuseSkyIrradianceDesaturationFactor", min: 0, max: 1.0, step: 0.01 }), jsx(BoundProperty, { label: "Additional Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "additionalDiffuseSkyIrradianceIntensity", min: 0, max: 100000.0, step: 1 }), jsx(BoundProperty, { label: "Additional Color", component: Color3PropertyLine, target: atmosphere, propertyKey: "additionalDiffuseSkyIrradianceColor" })] }));
4466
+ };
4467
+ const RenderingOptionsProperties = (props) => {
4468
+ const { entity: atmosphere } = props;
4469
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Linear Space Output", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isLinearSpaceComposition" }), jsx(BoundProperty, { label: "Linear Space Light", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isLinearSpaceLight" }), jsx(BoundProperty, { label: "Use LUT for Sky (Optimization)", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isSkyViewLutEnabled" }), jsx(BoundProperty, { label: "Use LUT for Aerial Perspective (Optimization)", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isAerialPerspectiveLutEnabled" })] }));
4470
+ };
4471
+
4472
+ const AtmospherePropertiesServiceDefinition = {
4473
+ friendlyName: "Atmosphere Properties",
4474
+ consumes: [PropertiesServiceIdentity, SelectionServiceIdentity, SceneContextIdentity],
4475
+ factory: (propertiesService, selectionService, sceneContext) => {
4476
+ const scene = sceneContext.currentScene;
4477
+ if (!scene) {
4478
+ return undefined;
4479
+ }
4480
+ const atmosphereContentRegistration = propertiesService.addSectionContent({
4481
+ key: "Atmosphere Properties",
4482
+ predicate: (entity) => entity instanceof Atmosphere,
4483
+ content: [
4484
+ {
4485
+ section: "General",
4486
+ component: ({ context }) => jsx(GeneralAtmosphereProperties, { entity: context }),
4487
+ },
4488
+ {
4489
+ section: "Scattering and Absorption",
4490
+ component: ({ context }) => jsx(ScatteringAndAbsorptionProperties, { entity: context }),
4491
+ },
4492
+ {
4493
+ section: "Multiple Scattering",
4494
+ component: ({ context }) => jsx(MultipleScatteringProperties, { entity: context }),
4495
+ },
4496
+ {
4497
+ section: "Aerial Perspective",
4498
+ component: ({ context }) => jsx(AerialPerspectiveProperties, { entity: context }),
4499
+ },
4500
+ {
4501
+ section: "Diffuse Sky Irradiance",
4502
+ component: ({ context }) => jsx(DiffuseIrradianceProperties, { entity: context }),
4503
+ },
4504
+ {
4505
+ section: "Rendering Options",
4506
+ component: ({ context }) => jsx(RenderingOptionsProperties, { entity: context }),
4507
+ },
4508
+ ],
4509
+ });
4510
+ return {
4511
+ dispose: () => {
4512
+ atmosphereContentRegistration.dispose();
4513
+ },
4514
+ };
4515
+ },
4516
+ };
4517
+
4518
+ const ArcRotateCameraTransformProperties = (props) => {
4519
+ const { camera, settings } = props;
4520
+ const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters(settings);
4521
+ const lowerAlphaLimit = useProperty(camera, "lowerAlphaLimit") ?? 0;
4522
+ const upperAlphaLimit = useProperty(camera, "upperAlphaLimit") ?? Math.PI * 2;
4523
+ const lowerBetaLimit = useProperty(camera, "lowerBetaLimit") ?? -Math.PI;
4524
+ const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? Math.PI;
4525
+ const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
4526
+ const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
4527
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Alpha", description: `Horizontal angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "alpha", min: toDisplayAngle(lowerAlphaLimit), max: toDisplayAngle(upperAlphaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Beta", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "beta", min: toDisplayAngle(lowerBetaLimit), max: toDisplayAngle(upperBetaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), lowerRadiusLimit != null && upperRadiusLimit != null ? (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: lowerRadiusLimit, max: upperRadiusLimit, step: 0.01 })) : (jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: 0, step: 0.01 }))] }));
4528
+ };
4529
+ const ArcRotateCameraControlProperties = (props) => {
4530
+ const { camera } = props;
4531
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angular Sensitivity X", target: camera, propertyKey: "angularSensibilityX" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angular Sensitivity Y", target: camera, propertyKey: "angularSensibilityY" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Panning Sensitivity", target: camera, propertyKey: "panningSensibility" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Pinch Delta Percentage", target: camera, propertyKey: "pinchDeltaPercentage" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Wheel Delta Percentage", target: camera, propertyKey: "wheelDeltaPercentage" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Natural Pinch Zoom", target: camera, propertyKey: "useNaturalPinchZoom" })] }));
4532
+ };
4533
+ const ArcRotateCameraCollisionProperties = (props) => {
4534
+ const { camera } = props;
4535
+ const collisionRadius = useProperty(camera, "collisionRadius");
4536
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Check Collisions", target: camera, propertyKey: "checkCollisions" }), jsx(Vector3PropertyLine, { label: "Collision Radius", value: collisionRadius, onChange: (val) => (camera.collisionRadius = val) })] }));
4537
+ };
4538
+ const ArcRotateCameraLimitsProperties = (props) => {
4539
+ const { camera } = props;
4540
+ // TODO-Iv2: Update defaultValues
4541
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Alpha Limit", target: camera, propertyKey: "lowerAlphaLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Alpha Limit", target: camera, propertyKey: "upperAlphaLimit", nullable: true, defaultValue: Infinity }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Beta Limit", target: camera, propertyKey: "lowerBetaLimit", nullable: true, defaultValue: -Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Beta Limit", target: camera, propertyKey: "upperBetaLimit", nullable: true, defaultValue: Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Radius Limit", target: camera, propertyKey: "lowerRadiusLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Radius Limit", target: camera, propertyKey: "upperRadiusLimit", nullable: true, defaultValue: 100 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Target Y Limit", target: camera, propertyKey: "lowerTargetYLimit" })] }));
4542
+ };
4543
+ const ArcRotateCameraBehaviorsProperties = (props) => {
4544
+ const { camera } = props;
4545
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Auto Rotation", target: camera, propertyKey: "useAutoRotationBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Bouncing", target: camera, propertyKey: "useBouncingBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Framing", target: camera, propertyKey: "useFramingBehavior" })] }));
4546
+ };
4547
+
4548
+ const useDropdownStyles = makeStyles({
4549
+ dropdown: { ...UniformWidthStyling, minWidth: CustomTokens.inputWidth },
4550
+ });
4551
+ /**
4552
+ * Renders a fluent UI dropdown component for the options passed in, and an additional 'Not Defined' option if null is set to true
4553
+ * This component can handle both null and undefined values
4554
+ * @param props
4555
+ * @returns dropdown component
4556
+ */
4557
+ const Dropdown = (props) => {
4558
+ const classes = useDropdownStyles();
4559
+ const { options, value } = props;
4560
+ const [defaultVal, setDefaultVal] = useState(props.value);
4561
+ useEffect(() => {
4562
+ setDefaultVal(value);
4563
+ }, [props.value]);
4564
+ return (jsx(Dropdown$1, { disabled: props.disabled, size: "medium", className: classes.dropdown, onOptionSelect: (evt, data) => {
4565
+ const value = typeof props.value === "number" ? Number(data.optionValue) : data.optionValue;
4566
+ if (value !== undefined) {
4567
+ setDefaultVal(value);
4568
+ props.onChange(value);
4569
+ }
4570
+ }, selectedOptions: [defaultVal.toString()], value: options.find((o) => o.value === defaultVal)?.label, children: options.map((option) => (jsx(Option, { className: classes.dropdown, value: option.value.toString(), disabled: false, children: option.label }, option.label))) }));
4571
+ };
4572
+ const NumberDropdown = Dropdown;
4573
+ const StringDropdown = Dropdown;
4574
+
4575
+ /**
4576
+ * Wraps a dropdown in a property line
4577
+ * @param props - PropertyLineProps and DropdownProps
4578
+ * @returns property-line wrapped dropdown
4579
+ */
4339
4580
  const DropdownPropertyLine = forwardRef((props, ref) => {
4340
4581
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props }) }));
4341
4582
  });
@@ -4386,7 +4627,7 @@ const FreeCameraTransformProperties = (props) => {
4386
4627
  const position = useProperty(camera, "position");
4387
4628
  const rotation = useProperty(camera, "rotation");
4388
4629
  const quatRotation = useProperty(camera, "rotationQuaternion");
4389
- return (jsxs(Fragment, { children: [jsx(Vector3PropertyLine, { label: "Position", value: position, onChange: (value) => (camera.position = value) }), quatRotation ? (jsx(QuaternionPropertyLine, { label: "Rotation (Quaternion)", value: quatRotation, onChange: (val) => (camera.rotationQuaternion = val), useDegrees: useDegrees }, "QuaternionRotationTransform")) : (jsx(RotationVectorPropertyLine, { label: "Rotation", value: rotation, onChange: (val) => (camera.rotation = val), useDegrees: useDegrees }, "RotationTransform"))] }));
4630
+ return (jsxs(Fragment, { children: [jsx(Vector3PropertyLine, { label: "Position", value: position, onChange: (value) => (camera.position = value) }), quatRotation ? (jsx(QuaternionPropertyLine, { label: "Rotation (Quat)", value: quatRotation, onChange: (val) => (camera.rotationQuaternion = val), useDegrees: useDegrees }, "QuaternionRotationTransform")) : (jsx(RotationVectorPropertyLine, { label: "Rotation", value: rotation, onChange: (val) => (camera.rotation = val), useDegrees: useDegrees }, "RotationTransform"))] }));
4390
4631
  };
4391
4632
  const FreeCameraControlProperties = (props) => {
4392
4633
  const { camera } = props;
@@ -4551,234 +4792,55 @@ const EffectLayerPropertiesServiceDefinition = {
4551
4792
  consumes: [PropertiesServiceIdentity],
4552
4793
  factory: (propertiesService) => {
4553
4794
  // TODO: Add content registrations for each section and for each type in the EffectLayer class hierarchy.
4554
- return {
4555
- dispose: () => {
4556
- // TODO: Dispose content registrations.
4557
- },
4558
- };
4559
- },
4560
- };
4561
-
4562
- const FrameGraphTaskProperties = (props) => {
4563
- const { frameGraph } = props;
4564
- const tasks = frameGraph.tasks;
4565
- return (jsx(Fragment, { children: tasks.length > 0 &&
4566
- tasks.map((task, i) => {
4567
- return jsx(TextPropertyLine, { label: i + 1 + ". " + task.name, value: "" }, "task" + i);
4568
- }) }));
4569
- };
4570
- const FrameGraphGeneralProperties = (props) => {
4571
- const { frameGraph } = props;
4572
- const isSceneFrameGraph = useProperty(frameGraph.scene, "frameGraph");
4573
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Optimize Texture Allocation", description: "Whether to optimize texture allocation.", target: frameGraph, propertyKey: "optimizeTextureAllocation" }), isSceneFrameGraph !== frameGraph && jsx(ButtonLine, { onClick: () => (frameGraph.scene.frameGraph = frameGraph), label: "Set as scene's frame graph" }), jsx(ButtonLine, { label: "Edit Graph", onClick: () => {
4574
- void frameGraph.getLinkedNodeRenderGraph().edit({ nodeRenderGraphEditorConfig: { hostScene: frameGraph.scene } });
4575
- } })] }));
4576
- };
4577
-
4578
- const FrameGraphPropertiesServiceDefinition = {
4579
- friendlyName: "Frame Graph Properties",
4580
- consumes: [PropertiesServiceIdentity],
4581
- factory: (propertiesService) => {
4582
- const frameGraphContentRegistration = propertiesService.addSectionContent({
4583
- key: "Frame Graph General Properties",
4584
- predicate: (entity) => entity instanceof FrameGraph,
4585
- content: [
4586
- {
4587
- section: "General",
4588
- component: ({ context }) => jsx(FrameGraphGeneralProperties, { frameGraph: context }),
4589
- },
4590
- {
4591
- section: "Tasks",
4592
- component: ({ context }) => jsx(FrameGraphTaskProperties, { frameGraph: context }),
4593
- },
4594
- ],
4595
- });
4596
- return {
4597
- dispose: () => {
4598
- frameGraphContentRegistration.dispose();
4599
- },
4600
- };
4601
- },
4602
- };
4603
-
4604
- const useColorPickerStyles = makeStyles({
4605
- colorPickerContainer: {
4606
- width: "325px",
4607
- display: "flex",
4608
- flexDirection: "column",
4609
- gap: tokens.spacingVerticalMNudge, // 10px
4610
- overflow: "visible",
4611
- },
4612
- previewColor: {
4613
- width: "50px",
4614
- height: "50px",
4615
- borderRadius: tokens.borderRadiusMedium,
4616
- border: `${tokens.spacingVerticalXXS} solid ${tokens.colorNeutralShadowKeyLighter}`,
4617
- "@media (forced-colors: active)": {
4618
- forcedColorAdjust: "none", // ensures elmement maintains color in high constrast mode
4619
- },
4620
- },
4621
- row: {
4622
- display: "flex",
4623
- flexDirection: "row",
4624
- gap: tokens.spacingVerticalM, // 12px
4625
- alignItems: "center",
4626
- },
4627
- colorFieldWrapper: {
4628
- display: "flex",
4629
- flexDirection: "column",
4630
- gap: tokens.spacingVerticalSNudge, // 6px
4631
- },
4632
- input: {
4633
- width: "80px",
4634
- },
4635
- spinButton: {
4636
- width: "50px",
4637
- },
4638
- container: {
4639
- display: "flex",
4640
- flexDirection: "column",
4641
- gap: tokens.spacingVerticalL, // 16px
4642
- },
4643
- });
4644
- const ColorPickerPopup = (props) => {
4645
- const classes = useColorPickerStyles();
4646
- const [color, setColor] = useState(props.value);
4647
- const [popoverOpen, setPopoverOpen] = useState(false);
4648
- useEffect(() => {
4649
- setColor(props.value); // Ensures the trigger color updates when props.value changes
4650
- }, [props.value]);
4651
- const handleColorPickerChange = (_, data) => {
4652
- let color = Color3.FromHSV(data.color.h, data.color.s, data.color.v);
4653
- if (props.value instanceof Color4) {
4654
- color = Color4.FromColor3(color, data.color.a ?? 1);
4655
- }
4656
- handleChange(color);
4657
- };
4658
- const handleChange = (newColor) => {
4659
- setColor(newColor);
4660
- props.onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
4661
- };
4662
- return (jsxs(Popover, { positioning: {
4663
- align: "start",
4664
- overflowBoundary: document.body,
4665
- autoSize: true,
4666
- }, open: popoverOpen, trapFocus: true, onOpenChange: (_, data) => setPopoverOpen(data.open), children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: "small", color: color.toHexString(), value: color.toHexString().slice(1) }) }), jsx(PopoverSurface, { children: jsxs("div", { className: classes.colorPickerContainer, children: [jsxs(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.container, children: [jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(InputHexField, { title: "Gamma Hex", value: color, isLinearMode: props.isLinearMode, onChange: handleChange }), jsx(InputHexField, { title: "Linear Hex", linearHex: true, isLinearMode: props.isLinearMode, value: color, onChange: handleChange })] }), jsxs("div", { className: classes.row, 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.row, 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 })] })] })] }) })] }));
4667
- };
4668
- const HEX_REGEX = RegExp(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/);
4669
- /**
4670
- * Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
4671
- * When the hex color is changed by user, component calculates the new Color3/4 value and calls onChange
4672
- *
4673
- * Component uses the isLinearMode boolean to display an informative label regarding linear / gamma space
4674
- * @param props - The properties for the InputHexField component.
4675
- * @returns
4676
- */
4677
- const InputHexField = (props) => {
4678
- const styles = useColorPickerStyles();
4679
- const { title, value, onChange, linearHex, isLinearMode } = props;
4680
- return (jsx("div", { className: styles.colorFieldWrapper, children: jsx(TextInput, { disabled: linearHex ? !isLinearMode : false, className: styles.input, value: linearHex ? value.toLinearSpace().toHexString() : value.toHexString(), validator: (val) => val != "" && HEX_REGEX.test(val), onChange: (val) => (linearHex ? onChange(Color3.FromHexString(val).toGammaSpace()) : onChange(Color3.FromHexString(val))), infoLabel: title
4681
- ? {
4682
- label: title,
4683
- // If not representing a linearHex, no info is needed.
4684
- info: !props.linearHex ? undefined : !isLinearMode ? ( // If representing a linear hex but we are in gammaMode, simple message explaining why linearHex is disabled
4685
- jsx(Fragment, { children: " This color picker is attached to an entity whose color is stored in gamma space, so we are showing linear hex in disabled view " })) : (
4686
- // If representing a linear hex and we are in linearMode, give information about how to use these hex values
4687
- jsxs(Fragment, { children: ["This color picker is attached to an entity whose color is stored in linear space (ex: PBR Material), and Babylon converts the color to gamma space before rendering on screen because the human eye is best at processing colors in gamma space. We thus also want to display the color picker in gamma space so that the color chosen here will match the color seen in your entity.", jsx("br", {}), "If you want to copy/paste the HEX into your code, you can either use", jsx(Body1Strong, { children: "Color3.FromHexString(LINEAR_HEX)" }), jsx("br", {}), "or", jsx("br", {}), jsx(Body1Strong, { children: "Color3.FromHexString(GAMMA_HEX).toLinearSpace()" }), jsx("br", {}), jsx("br", {}), jsx(Link, { href: "https://doc.babylonjs.com/preparingArtForBabylon/controllingColorSpace/", children: " Read more in our docs! " })] })),
4688
- }
4689
- : undefined }) }));
4690
- };
4691
- const InputRgbField = (props) => {
4692
- const { value, onChange, title, rgbKey } = props;
4693
- const classes = useColorPickerStyles();
4694
- const handleChange = useCallback((val) => {
4695
- const newColor = value.clone();
4696
- newColor[rgbKey] = val / 255.0; // Convert to 0-1 range
4697
- onChange(newColor);
4698
- }, [value, onChange, rgbKey]);
4699
- return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { title: title, infoLabel: title ? { label: title } : undefined, className: classes.spinButton, min: 0, max: 255, value: Math.round(value[rgbKey] * 255), forceInt: true, onChange: handleChange }) }));
4700
- };
4701
- function rgbaToHsv(color) {
4702
- const c = new Color3(color.r, color.g, color.b);
4703
- const hsv = c.toHSV();
4704
- return { h: hsv.r, s: hsv.g, v: hsv.b, a: color.a };
4705
- }
4706
- /**
4707
- * In the HSV (Hue, Saturation, Value) color model, Hue (H) ranges from 0 to 360 degrees, representing the color's position on the color wheel.
4708
- * Saturation (S) ranges from 0 to 100%, indicating the intensity or purity of the color, with 0 being shades of gray and 100 being a fully saturated color.
4709
- * Value (V) ranges from 0 to 100%, representing the brightness of the color, with 0 being black and 100 being the brightest.
4710
- * @param props - The properties for the InputHsvField component.
4711
- */
4712
- const InputHsvField = (props) => {
4713
- const { value, title, hsvKey, max, onChange, scale = 1 } = props;
4714
- const classes = useColorPickerStyles();
4715
- const handleChange = useCallback((val) => {
4716
- // Convert current color to HSV, update the new hsv value, then call onChange prop
4717
- const hsv = rgbaToHsv(value);
4718
- hsv[hsvKey] = val / scale;
4719
- let newColor = Color3.FromHSV(hsv.h, hsv.s, hsv.v);
4720
- if (value instanceof Color4) {
4721
- newColor = Color4.FromColor3(newColor, value.a ?? 1);
4722
- }
4723
- props.onChange(newColor);
4724
- }, [value, onChange, hsvKey, scale]);
4725
- return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { infoLabel: title ? { label: title } : undefined, title: title, className: classes.spinButton, min: 0, max: max, value: Math.round(rgbaToHsv(value)[hsvKey] * scale), forceInt: true, onChange: handleChange }) }));
4795
+ return {
4796
+ dispose: () => {
4797
+ // TODO: Dispose content registrations.
4798
+ },
4799
+ };
4800
+ },
4726
4801
  };
4727
- /**
4728
- * Displays the alpha value of a color, either in the disabled state (if color is Color3) or as a spin button (if color is Color4).
4729
- * @param props
4730
- * @returns
4731
- */
4732
- const InputAlphaField = (props) => {
4733
- const classes = useColorPickerStyles();
4734
- const { color, onChange } = props;
4735
- const handleChange = useCallback((value) => {
4736
- if (Number.isNaN(value) || value < 0 || value > 1) {
4737
- return;
4738
- }
4739
- if (color instanceof Color4) {
4740
- const newColor = color.clone();
4741
- newColor.a = value;
4742
- return newColor;
4743
- }
4744
- else {
4745
- return Color4.FromColor3(color, value);
4746
- }
4747
- }, [onChange]);
4748
- return (jsx("div", { className: classes.colorFieldWrapper, children: jsx(SpinButton, { disabled: color instanceof Color3, min: 0, max: 1, className: classes.spinButton, value: color instanceof Color3 ? 1 : color.a, step: 0.01, onChange: handleChange, infoLabel: {
4749
- label: "Alpha",
4750
- info: color instanceof Color3 ? (jsx(Fragment, { children: "Because this color picker is representing a Color3, we do not permit modifying alpha from the color picker. You can however modify the entity's alpha property directly, either in code via entity.alpha OR via inspector's transparency section." })) : undefined,
4751
- } }) }));
4802
+
4803
+ const FrameGraphTaskProperties = (props) => {
4804
+ const { frameGraph } = props;
4805
+ const tasks = frameGraph.tasks;
4806
+ return (jsx(Fragment, { children: tasks.length > 0 &&
4807
+ tasks.map((task, i) => {
4808
+ return jsx(TextPropertyLine, { label: i + 1 + ". " + task.name, value: "" }, "task" + i);
4809
+ }) }));
4810
+ };
4811
+ const FrameGraphGeneralProperties = (props) => {
4812
+ const { frameGraph } = props;
4813
+ const isSceneFrameGraph = useProperty(frameGraph.scene, "frameGraph");
4814
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Optimize Texture Allocation", description: "Whether to optimize texture allocation.", target: frameGraph, propertyKey: "optimizeTextureAllocation" }), isSceneFrameGraph !== frameGraph && jsx(ButtonLine, { onClick: () => (frameGraph.scene.frameGraph = frameGraph), label: "Set as scene's frame graph" }), jsx(ButtonLine, { label: "Edit Graph", onClick: () => {
4815
+ void frameGraph.getLinkedNodeRenderGraph().edit({ nodeRenderGraphEditorConfig: { hostScene: frameGraph.scene } });
4816
+ } })] }));
4752
4817
  };
4753
4818
 
4754
- /**
4755
- * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
4756
- * The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
4757
- * @param props - PropertyLine props, replacing children with a color object so that we can properly display the color
4758
- * @returns Component wrapping a colorPicker component with a property line
4759
- */
4760
- const ColorPropertyLine = forwardRef((props, ref) => {
4761
- const [color, setColor] = useState(props.value);
4762
- const onSliderChange = (value, key) => {
4763
- let newColor;
4764
- if (key === "a") {
4765
- newColor = Color4.FromColor3(color, value);
4766
- }
4767
- else {
4768
- newColor = color.clone();
4769
- newColor[key] = value / 255;
4770
- }
4771
- setColor(newColor); // Create a new object to trigger re-render
4772
- props.onChange(newColor);
4773
- };
4774
- const onColorPickerChange = (newColor) => {
4775
- setColor(newColor);
4776
- props.onChange(newColor);
4777
- };
4778
- return (jsx(PropertyLine, { ref: ref, ...props, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "b") }), color instanceof Color4 && jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, onChange: (value) => onSliderChange(value, "a") })] }), children: jsx(ColorPickerPopup, { ...props, onChange: onColorPickerChange, value: color }) }));
4779
- });
4780
- const Color3PropertyLine = ColorPropertyLine;
4781
- const Color4PropertyLine = ColorPropertyLine;
4819
+ const FrameGraphPropertiesServiceDefinition = {
4820
+ friendlyName: "Frame Graph Properties",
4821
+ consumes: [PropertiesServiceIdentity],
4822
+ factory: (propertiesService) => {
4823
+ const frameGraphContentRegistration = propertiesService.addSectionContent({
4824
+ key: "Frame Graph General Properties",
4825
+ predicate: (entity) => entity instanceof FrameGraph,
4826
+ content: [
4827
+ {
4828
+ section: "General",
4829
+ component: ({ context }) => jsx(FrameGraphGeneralProperties, { frameGraph: context }),
4830
+ },
4831
+ {
4832
+ section: "Tasks",
4833
+ component: ({ context }) => jsx(FrameGraphTaskProperties, { frameGraph: context }),
4834
+ },
4835
+ ],
4836
+ });
4837
+ return {
4838
+ dispose: () => {
4839
+ frameGraphContentRegistration.dispose();
4840
+ },
4841
+ };
4842
+ },
4843
+ };
4782
4844
 
4783
4845
  const AreaLightSetupProperties = ({ context: areaLight }) => {
4784
4846
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: areaLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: areaLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: areaLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Width", component: NumberInputPropertyLine, target: areaLight, propertyKey: "width" }), jsx(BoundProperty, { label: "Height", component: NumberInputPropertyLine, target: areaLight, propertyKey: "height" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: areaLight, propertyKey: "intensity" })] }));
@@ -6084,10 +6146,10 @@ const AttractorComponent = (props) => {
6084
6146
  useEffect(() => {
6085
6147
  impostor.scaling.setAll(impostorScale);
6086
6148
  }, [impostor, impostorScale]);
6087
- return (jsxs("div", { className: classes.container, children: [jsx(SyncedSliderInput, { value: attractor.strength, onChange: (value) => (attractor.strength = value), min: -10, max: 10, step: 0.1 }), jsx(ToggleButton, { title: "Show / hide particle attractor.", enabledIcon: EyeFilled, disabledIcon: EyeOffFilled, value: shown, onChange: (show) => {
6149
+ return (jsxs("div", { className: classes.container, children: [jsx(SyncedSliderInput, { value: attractor.strength, onChange: (value) => (attractor.strength = value), min: -10, max: 10, step: 0.1 }), jsx(ToggleButton, { title: "Show / hide particle attractor.", checkedIcon: EyeFilled, uncheckedIcon: EyeOffFilled, value: shown, onChange: (show) => {
6088
6150
  show ? (impostor.visibility = 1) : (impostor.visibility = 0);
6089
6151
  setShown(show);
6090
- } }), jsx(ToggleButton, { title: "Add / remove position gizmo from particle attractor", enabledIcon: ArrowMoveFilled, value: isControlled(impostor), onChange: (control) => onControl(control ? impostor : undefined) })] }));
6152
+ } }), jsx(ToggleButton, { title: "Add / remove position gizmo from particle attractor", checkedIcon: ArrowMoveFilled, value: isControlled(impostor), onChange: (control) => onControl(control ? impostor : undefined) })] }));
6091
6153
  };
6092
6154
 
6093
6155
  // For each Attractor, create a listItem consisting of the attractor and its debugging impostor mesh
@@ -7066,7 +7128,7 @@ const TransformProperties = (props) => {
7066
7128
  const { transform, settings } = props;
7067
7129
  const quatRotation = useQuaternionProperty(transform, "rotationQuaternion");
7068
7130
  const useDegrees = useObservableState(() => settings.useDegrees, settings.settingsChangedObservable);
7069
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: transform, propertyKey: "position" }), quatRotation ? (jsx(QuaternionPropertyLine, { label: "Rotation (Quaternion)", value: quatRotation, onChange: (val) => (transform.rotationQuaternion = val), useDegrees: useDegrees }, "QuaternionRotationTransform")) : (jsx(BoundProperty, { component: RotationVectorPropertyLine, label: "Rotation", target: transform, propertyKey: "rotation", useDegrees: useDegrees })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Scaling", target: transform, propertyKey: "scaling" })] }));
7131
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: transform, propertyKey: "position" }), quatRotation ? (jsx(QuaternionPropertyLine, { label: "Rotation (Quat)", value: quatRotation, onChange: (val) => (transform.rotationQuaternion = val), useDegrees: useDegrees }, "QuaternionRotationTransform")) : (jsx(BoundProperty, { component: RotationVectorPropertyLine, label: "Rotation", target: transform, propertyKey: "rotation", useDegrees: useDegrees })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Scaling", target: transform, propertyKey: "scaling" })] }));
7070
7132
  };
7071
7133
 
7072
7134
  const TransformPropertiesServiceDefinition = {
@@ -7171,6 +7233,49 @@ const AnimationGroupExplorerServiceDefinition = {
7171
7233
  },
7172
7234
  };
7173
7235
 
7236
+ const AtmosphereExplorerServiceDefinition = {
7237
+ friendlyName: "Atmosphere Explorer",
7238
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
7239
+ factory: (sceneExplorerService, sceneContext) => {
7240
+ const scene = sceneContext.currentScene;
7241
+ if (!scene) {
7242
+ return undefined;
7243
+ }
7244
+ const sectionRegistration = sceneExplorerService.addSection({
7245
+ displayName: "Atmosphere",
7246
+ order: 1300 /* DefaultSectionsOrder.Atmosphere */,
7247
+ getRootEntities: () => (scene.getExternalData("atmosphere") ? [scene.getExternalData("atmosphere")] : []),
7248
+ getEntityDisplayInfo: (atmosphere) => {
7249
+ const onChangeObservable = new Observable();
7250
+ const nameHookToken = InterceptProperty(atmosphere, "name", {
7251
+ afterSet: () => {
7252
+ onChangeObservable.notifyObservers();
7253
+ },
7254
+ });
7255
+ return {
7256
+ get name() {
7257
+ return atmosphere.name;
7258
+ },
7259
+ onChange: onChangeObservable,
7260
+ dispose: () => {
7261
+ nameHookToken.dispose();
7262
+ onChangeObservable.clear();
7263
+ },
7264
+ };
7265
+ },
7266
+ entityIcon: () => jsx(WeatherSunnyLowFilled, {}),
7267
+ // TODO in order for inspector UX to display atmosphere created after inspector is created
7268
+ getEntityAddedObservables: () => [],
7269
+ getEntityRemovedObservables: () => [],
7270
+ });
7271
+ return {
7272
+ dispose: () => {
7273
+ sectionRegistration.dispose();
7274
+ },
7275
+ };
7276
+ },
7277
+ };
7278
+
7174
7279
  const EffectLayerExplorerServiceDefinition = {
7175
7280
  friendlyName: "Effect Layer Explorer",
7176
7281
  consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
@@ -7913,7 +8018,7 @@ const PickingToolbar = (props) => {
7913
8018
  /* No-op */
7914
8019
  };
7915
8020
  }, [pickingEnabled, sceneElement, ignoreBackfaces]);
7916
- return (sceneElement && (jsx(ToggleButton, { title: `${pickingEnabled ? "Disable" : "Enable"} Picking`, enabledIcon: TargetRegular, value: pickingEnabled, onChange: () => setPickingEnabled((prev) => !prev) })));
8021
+ return (sceneElement && jsx(ToggleButton, { title: `${pickingEnabled ? "Disable" : "Enable"} Picking`, checkedIcon: TargetRegular, value: pickingEnabled, onChange: setPickingEnabled }));
7917
8022
  };
7918
8023
 
7919
8024
  const PickingServiceDefinition = {
@@ -7935,120 +8040,6 @@ const PickingServiceDefinition = {
7935
8040
  },
7936
8041
  };
7937
8042
 
7938
- const GeneralAtmosphereProperties = (props) => {
7939
- const { entity: atmosphere } = props;
7940
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Planet Radius (km)", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "planetRadius", min: 1000.0, max: 10000.0, step: 1 }), jsx(BoundProperty, { label: "Atmosphere Thickness (km)", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "atmosphereThickness", min: 1.0, max: 200.0, step: 1 })] }));
7941
- };
7942
- const ScatteringAndAbsorptionProperties = (props) => {
7943
- const { entity: atmosphere } = props;
7944
- return (jsxs(Fragment, { children: [jsx(PropertyLine, { label: "Rayleigh Scattering", expandByDefault: true, description: "Rayleigh scattering is the scattering of light off of the molecules of the atmosphere. It is the main reason why the sky is blue. Increasing the Rayleigh scattering coefficient will result in a bluer sky.", expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "rayleighScatteringScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakRayleighScattering", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Mie Scattering", description: "Mie scattering is the scattering of light off of the larger particles in the atmosphere, such as dust and water droplets. It is responsible for the white appearance of clouds and the haziness of the sky. Increasing the Mie scattering coefficient will result in a whiter sky.", expandByDefault: true, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "mieScatteringScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakMieScattering", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Mie Absorption", description: "Mie absorption is the absorption of light by the larger particles in the atmosphere, such as dust and water droplets. It is responsible for the dimming of the sun during haze and fog. Increasing mie absorption coefficient will result in visually darker skies.", expandByDefault: true, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "mieAbsorptionScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakMieAbsorption", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01, unit: "Mm" })] }) }), jsx(PropertyLine, { label: "Ozone Absorption", expandByDefault: true, description: "Ozone absorption is the absorption of light by ozone molecules in the atmosphere. Increasing ozone absorption coefficient will result in visually darker skies.", expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Scale", component: SyncedSliderPropertyLine, target: atmosphere.physicalProperties, propertyKey: "ozoneAbsorptionScale", min: 0.0, max: 5.0, step: 0.01 }), jsx(BoundProperty, { label: "Coefficient", component: Vector3PropertyLine, target: atmosphere.physicalProperties, propertyKey: "peakOzoneAbsorption", convertTo: (value) => value.scale(1000), convertFrom: (value) => value.scale(0.001), min: 0, step: 0.01 })] }) })] }));
7945
- };
7946
- const MultipleScatteringProperties = (props) => {
7947
- const { entity: atmosphere } = props;
7948
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "multiScatteringIntensity", min: 0, max: 5.0, step: 0.1 }), jsx(BoundProperty, { label: "Minimum Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "minimumMultiScatteringIntensity", min: 0.0, max: 0.1, step: 0.0001 }), jsx(BoundProperty, { label: "Minimum Color", component: Color3PropertyLine, target: atmosphere, propertyKey: "minimumMultiScatteringColor" }), jsx(BoundProperty, { label: "Ground Albedo", component: Color3PropertyLine, target: atmosphere, propertyKey: "groundAlbedo" })] }));
7949
- };
7950
- const AerialPerspectiveProperties = (props) => {
7951
- const { entity: atmosphere } = props;
7952
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveIntensity", min: 0, max: 5.0, step: 0.1 }), jsx(BoundProperty, { label: "Transmittance Scale", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveTransmittanceScale", min: 0, max: 2.0, step: 0.01 }), jsx(BoundProperty, { label: "Saturation", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "aerialPerspectiveSaturation", min: 0, max: 2.0, step: 0.01 })] }));
7953
- };
7954
- const DiffuseIrradianceProperties = (props) => {
7955
- const { entity: atmosphere } = props;
7956
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "diffuseSkyIrradianceIntensity", min: 0, max: 5.0, step: 0.001 }), jsx(BoundProperty, { label: "Desaturation", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "diffuseSkyIrradianceDesaturationFactor", min: 0, max: 1.0, step: 0.01 }), jsx(BoundProperty, { label: "Additional Intensity", component: SyncedSliderPropertyLine, target: atmosphere, propertyKey: "additionalDiffuseSkyIrradianceIntensity", min: 0, max: 100000.0, step: 1 }), jsx(BoundProperty, { label: "Additional Color", component: Color3PropertyLine, target: atmosphere, propertyKey: "additionalDiffuseSkyIrradianceColor" })] }));
7957
- };
7958
- const RenderingOptionsProperties = (props) => {
7959
- const { entity: atmosphere } = props;
7960
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Linear Space Output", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isLinearSpaceComposition" }), jsx(BoundProperty, { label: "Linear Space Light", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isLinearSpaceLight" }), jsx(BoundProperty, { label: "Use LUT for Sky (Optimization)", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isSkyViewLutEnabled" }), jsx(BoundProperty, { label: "Use LUT for Aerial Perspective (Optimization)", component: SwitchPropertyLine, target: atmosphere, propertyKey: "isAerialPerspectiveLutEnabled" })] }));
7961
- };
7962
-
7963
- const AtmospherePropertiesServiceDefinition = {
7964
- friendlyName: "Atmosphere Properties",
7965
- consumes: [PropertiesServiceIdentity, SelectionServiceIdentity, SceneContextIdentity],
7966
- factory: (propertiesService, selectionService, sceneContext) => {
7967
- const scene = sceneContext.currentScene;
7968
- if (!scene) {
7969
- return undefined;
7970
- }
7971
- const atmosphereContentRegistration = propertiesService.addSectionContent({
7972
- key: "Atmosphere Properties",
7973
- predicate: (entity) => entity instanceof Atmosphere,
7974
- content: [
7975
- {
7976
- section: "General",
7977
- component: ({ context }) => jsx(GeneralAtmosphereProperties, { entity: context }),
7978
- },
7979
- {
7980
- section: "Scattering and Absorption",
7981
- component: ({ context }) => jsx(ScatteringAndAbsorptionProperties, { entity: context }),
7982
- },
7983
- {
7984
- section: "Multiple Scattering",
7985
- component: ({ context }) => jsx(MultipleScatteringProperties, { entity: context }),
7986
- },
7987
- {
7988
- section: "Aerial Perspective",
7989
- component: ({ context }) => jsx(AerialPerspectiveProperties, { entity: context }),
7990
- },
7991
- {
7992
- section: "Diffuse Sky Irradiance",
7993
- component: ({ context }) => jsx(DiffuseIrradianceProperties, { entity: context }),
7994
- },
7995
- {
7996
- section: "Rendering Options",
7997
- component: ({ context }) => jsx(RenderingOptionsProperties, { entity: context }),
7998
- },
7999
- ],
8000
- });
8001
- return {
8002
- dispose: () => {
8003
- atmosphereContentRegistration.dispose();
8004
- },
8005
- };
8006
- },
8007
- };
8008
-
8009
- const AtmosphereExplorerServiceDefinition = {
8010
- friendlyName: "Atmosphere Explorer",
8011
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
8012
- factory: (sceneExplorerService, sceneContext) => {
8013
- const scene = sceneContext.currentScene;
8014
- if (!scene) {
8015
- return undefined;
8016
- }
8017
- const sectionRegistration = sceneExplorerService.addSection({
8018
- displayName: "Atmosphere",
8019
- order: 1300 /* DefaultSectionsOrder.Atmosphere */,
8020
- getRootEntities: () => (scene.getExternalData("atmosphere") ? [scene.getExternalData("atmosphere")] : []),
8021
- getEntityDisplayInfo: (atmosphere) => {
8022
- const onChangeObservable = new Observable();
8023
- const nameHookToken = InterceptProperty(atmosphere, "name", {
8024
- afterSet: () => {
8025
- onChangeObservable.notifyObservers();
8026
- },
8027
- });
8028
- return {
8029
- get name() {
8030
- return atmosphere.name;
8031
- },
8032
- onChange: onChangeObservable,
8033
- dispose: () => {
8034
- nameHookToken.dispose();
8035
- onChangeObservable.clear();
8036
- },
8037
- };
8038
- },
8039
- entityIcon: () => jsx(WeatherSunnyLowFilled, {}),
8040
- // TODO in order for inspector UX to display atmosphere created after inspector is created
8041
- getEntityAddedObservables: () => [],
8042
- getEntityRemovedObservables: () => [],
8043
- });
8044
- return {
8045
- dispose: () => {
8046
- sectionRegistration.dispose();
8047
- },
8048
- };
8049
- },
8050
- };
8051
-
8052
8043
  let CurrentInspectorToken = null;
8053
8044
  function IsInspectorVisible() {
8054
8045
  return CurrentInspectorToken != null;
@@ -8066,8 +8057,6 @@ function _ShowInspector(scene, options) {
8066
8057
  enableClose: true,
8067
8058
  handleResize: true,
8068
8059
  enablePopup: true,
8069
- isExtensible: true,
8070
- isThemeable: true,
8071
8060
  ...options,
8072
8061
  };
8073
8062
  if (!scene) {
@@ -8214,8 +8203,9 @@ function _ShowInspector(scene, options) {
8214
8203
  // Additional services passed in to the Inspector.
8215
8204
  ...(options.serviceDefinitions ?? []),
8216
8205
  ],
8217
- isThemeable: options.isThemeable ?? true,
8218
- extensionFeeds: options.isExtensible ? [new BuiltInsExtensionFeed()] : [],
8206
+ themeMode: options.themeMode,
8207
+ showThemeSelector: options.showThemeSelector,
8208
+ extensionFeeds: [DefaultInspectorExtensionFeed, ...(options.extensionFeeds ?? [])],
8219
8209
  toolbarMode: "compact",
8220
8210
  sidePaneMode: options.embedMode ? "right" : "both",
8221
8211
  });
@@ -8494,5 +8484,5 @@ const Pane = (props) => {
8494
8484
  return (jsxs("div", { className: classes.rootDiv, children: [jsxs("div", { className: classes.header, children: [props.icon ? (jsx(props.icon, { className: classes.icon })) : (jsx("img", { className: classes.icon, id: "logo", src: "https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" })), jsx(Body1Strong, { id: "title", children: props.title })] }), props.children] }));
8495
8485
  };
8496
8486
 
8497
- export { Checkbox as $, MakePopoverTeachingMoment as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, GetPropertyDescriptor as G, IsPropertyReadonly as H, InterceptFunction as I, InterceptProperty as J, ConstructorFactory as K, LinkToEntityPropertyLine as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, SelectionServiceIdentity as O, Pane$1 as P, SelectionServiceDefinition as Q, SettingsContextIdentity as R, ShellServiceIdentity as S, ToolsServiceIdentity as T, IsInspectorVisible as U, ShowInspector as V, HideInspector as W, Inspector as X, AccordionSection as Y, Accordion as Z, Button as _, SceneContextIdentity as a, ColorPickerPopup as a0, InputHexField as a1, InputHsvField as a2, ComboBox as a3, DraggableLine as a4, Dropdown as a5, NumberDropdown as a6, StringDropdown as a7, FactorGradientComponent as a8, Color3GradientComponent as a9, Color4GradientComponent as aa, ColorStepGradientComponent as ab, InfoLabel as ac, List as ad, MessageBar as ae, PositionedPopover as af, SearchBar as ag, SearchBox as ah, SpinButton as ai, CalculatePrecision as aj, Switch as ak, SyncedSliderInput as al, Textarea as am, TextInput as an, ToggleButton as ao, FactorGradientList as ap, Color3GradientList as aq, Color4GradientList as ar, Pane as as, SwitchPropertyLine as b, SyncedSliderPropertyLine as c, PropertiesServiceIdentity as d, SceneExplorerServiceIdentity as e, SettingsServiceIdentity as f, StatsServiceIdentity as g, BoundProperty as h, TeachingMoment as i, useProperty as j, useVector3Property as k, useColor3Property as l, useColor4Property as m, useQuaternionProperty as n, MakePropertyHook as o, useInterceptObservable as p, useEventfulState as q, useObservableCollection as r, useOrderedObservableCollection as s, usePollingObservable as t, useObservableState as u, useResource as v, useAsyncResource as w, useAngleConverters as x, MakeTeachingMoment as y, MakeDialogTeachingMoment as z };
8498
- //# sourceMappingURL=index-B29Ify1o.js.map
8487
+ export { Button as $, MakePopoverTeachingMoment as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, GetPropertyDescriptor as G, IsPropertyReadonly as H, InterceptFunction as I, InterceptProperty as J, ConstructorFactory as K, LinkToEntityPropertyLine as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, SceneContextIdentity as O, Pane$1 as P, SelectionServiceIdentity as Q, SelectionServiceDefinition as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, SettingsContextIdentity as U, IsInspectorVisible as V, ShowInspector as W, HideInspector as X, Inspector as Y, AccordionSection as Z, Accordion as _, SyncedSliderPropertyLine as a, Checkbox as a0, ColorPickerPopup as a1, InputHexField as a2, InputHsvField as a3, ComboBox as a4, DraggableLine as a5, Dropdown as a6, NumberDropdown as a7, StringDropdown as a8, FactorGradientComponent as a9, Color3GradientComponent as aa, Color4GradientComponent as ab, ColorStepGradientComponent as ac, InfoLabel as ad, List as ae, MessageBar as af, PositionedPopover as ag, SearchBar as ah, SearchBox as ai, SpinButton as aj, CalculatePrecision as ak, Switch as al, SyncedSliderInput as am, Textarea as an, TextInput as ao, ToggleButton as ap, FactorGradientList as aq, Color3GradientList as ar, Color4GradientList as as, Pane as at, PropertiesServiceIdentity as b, SceneExplorerServiceIdentity as c, SettingsServiceIdentity as d, StatsServiceIdentity as e, ShellServiceIdentity as f, BoundProperty as g, TeachingMoment as h, BuiltInsExtensionFeed as i, useVector3Property as j, useColor3Property as k, useColor4Property as l, useQuaternionProperty as m, MakePropertyHook as n, useInterceptObservable as o, useEventfulState as p, useObservableState as q, useObservableCollection as r, useOrderedObservableCollection as s, usePollingObservable as t, useProperty as u, useResource as v, useAsyncResource as w, useAngleConverters as x, MakeTeachingMoment as y, MakeDialogTeachingMoment as z };
8488
+ //# sourceMappingURL=index-BTXdoz_s.js.map